Java单元测试总结

Posted ShuSheng007

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java单元测试总结相关的知识,希望对你有一定的参考价值。

[版权申明] 非商业目的注明出处可自由转载
博文地址:
出自:shusheng007

概述

关于为什么要做单元测试是老生常谈了,越是小公司越不重视单元测试,这是实践出来的最经济的做法。由于大部分人在小公司工作,所以单元测试从来就没有被真正的重视起来过。大部分时候你写单元测试,不是你觉得它有用而是因为公司要求写。由于大公司部门以及项目协作很复杂,加上人力物力相对富裕,所以勉强可以推行下去,小公司这么搞就是在找死!

本人最近被迫写了很多单元测试,所以在此总结一下相关的知识点,留作笔记

本文基于SpringBoot项目

单元测试概念

我们首先要清楚的理解什么是单元测试?那个单元是啥?在Java中那个单元一般指一个类,也就是说我们的测试是着眼于单个类的,而不是一个流程.

例如有如下代码:

public Class1{
	private Class2 c2;
    ...
	public void method1(){
	     ...
        String result = c2.method2();
    }
}

Class1依赖Class2,我们要给Class1写unit test的时候怎么办呢?因为UT的思想是看当前单元的功能是否正常而不管其他单元。对应到这里的话就是,Class1的UT只测试Class1的方法method1是否正常工作,而不管Class2的方法method2的情况。如果你要管Class2的方法那就没头了,因为它也可能依赖其他类,以此类推,无穷无尽,最后就变成集成测试了。

因为Class1的method1方法调用了Class2的方法,你不管怎么运行的通呢?答案就是Mock,让其按照我们的预期值通过。当然我们可以自己mock, 也可以使用框架帮助我们,这块Mockito开源库是当之无愧的扛把子。

测试项目

建立一个springboot的maven项目后,其会自动生成单元测试目录,如下所示

在这里插入图片描述
系统会生成一套与代码一致的测试目录结构

spring boot会自动引入测试相关的依赖如下

 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-test</artifactId>
     <scope>test</scope>
 </dependency>

spring-boot-starter-test 会引入各种类库,其中就包括Junit和 Mockito。

单元测试工具

工欲善其事必先利其器,在Java的单元测试领域主流工具为JUnit与Mockito。JUnit 负责写单元测试业务逻辑,Mockito负责mock依赖。

JUnit

JUnit 已经发展到了JUnit5,所以我们只记录JUnit5的用法。

JUnit5 包含3个部分:

  • JUnit Platform: 用于JVM上启动测试框架的基础服务,提供命令行,IDE和构建工具等方式执行测试的支持。

  • JUnit Jupiter:包含 JUnit 5 新的编程模型和扩展模型,主要就是用于编写测试代码和扩展代码。

  • JUnit Vintage:用于在JUnit 5 中兼容运行 JUnit3.x 和 JUnit4.x 的测试用例。

编码相关的由 JUnit Jupiter 负责。

写使用JUnit写单元测试非常简单,IDEA 内部甚至集成了JUnit,可以直接生成。

例如我们有如下代码需要写单元测试

public class Programmer {
    private final String content ="码字如飞";
    public String program(){
        return content;
    }
}

public class Company {
    private Programmer programmer;
    public Company(Programmer programmer) {
        this.programmer = programmer;
    }

    //计算测试与程序员的总人数
    public int getTotalStuff(int testers, int programmers){
       if(testers<0||programmers<0){
            throw new InvalidParameterException("不可为负数");
        }
        return testers + programmers;
    }
	//开始项目,依赖Programmer
    public void startProject(){
        System.out.println("开始项目");
       System.out.println(programmer.program());
    }
}

如果你使用IDEA的话,光标停在类名称上,点击Alt+Enter键,弹出菜单后选择Create Test,如下所示:

在这里插入图片描述
然后勾选上你要进行单元测试的方法,点击Ok按钮,IDEA就为你在相应的位置生成了单元测试的骨架代码。
在这里插入图片描述
生成的代码如下:

class CompanyTest {

    @BeforeEach
    void setUp() {
    }

    @AfterEach
    void tearDown() {
    }

    @Test
    void getTotalStuff() {
    }

    @Test
    void startProject() {
    }
}
  • @Test 表示那是一个单元测试方法
  • @BeforeEach 此方法在每个测试方法执行前都会执行
  • @AfterEach 此方法在每个测试方法执行后都会执行

JUnit 还有很多其他注解,我们接下来会讲到几个。

走到这一步立刻就会遇到另一个问题,单元测试测什么啊?单元测试的目的是验证你那个方法在各种情况下是否正常工作。一般可以分为算法测试和结果测试,算法测试是看你的方法是否按照预期的执行步骤执行了,而结果测试是看你的方法在给定输入时最终的输出是否符合预期。

如果你的代码写的比较垃圾,一个方法承担了特别多的职责,里面到处是逻辑分支,那么你这个方法进行算法测试就会非常空难,因为你要验证各个分支是否正常工作。

我们接下来尝试为下面的方法书写单元测试

public int getTotalStuff(int testers, int programmers){
    if(testers<0||programmers<0){
        throw new InvalidParameterException("不可为负数");
    }
    return testers + programmers;
}

对于上面的方法,我们一般会验证两个地方。第一档参数不合法时候的情况,第二相加结果。

class CompanyTest {
    private Company company;
    @BeforeEach
    void setUp() {
        System.out.println("每个方法之前执行");
        company = new Company(new Programmer());
    }

    @Test
    void getTotalStuff_InvalidParam() {
        int tester = -1;
        int programmer = 5;
        Assertions.assertThrows(InvalidParameterException.class, () -> company.getTotalStuff(tester, programmer));
    }

    @DisplayName("获取员工数成功方法")
    @Test
    void getTotalStuff_Success() {
        int tester = 1;
        int programmer = 5;
        Assertions.assertEquals(6, company.getTotalStuff(tester, programmer));
    }


    @Disabled
    @Test
    void startProject() {
        System.out.println("startProject");
    }
}

@DisplayName("获取员工数成功方法") 将修改测试方法在测试报告中的名称,如下图所示。通过@Disabled可以屏蔽某个测试方法。
在这里插入图片描述

JUnit 提供了Assertions类,里面有很多断言的静态方法,例如本例中断言抛异常和相等的方法。

关于JUnit更进一步的使用,这里推荐一个非常好的教程 Unit Testing with JUnit 5 - Tutorial

Mockito

Mockito一般都会伴随着JUnit出现,那么不使用Mockito可以完成UT吗?当然可以。如果不可以的话在没有Mockito的时候人们怎么办呢?只是因为用了它使生活变的更简单了。

例如本例,我们需要一个Company的对象怎么办呢?只能自己mock一个,使用Mockito的话就它就可以帮我们mock了,如下图:

@ExtendWith(MockitoExtension.class)
class CompanyTest {
    @Mock
    private Programmer programmer;

    @InjectMocks
    private Company company;

    
    @BeforeEach
    void setUp() {
        System.out.println("每个方法之前执行");
//        company = new Company(new Programmer());
    }

...
//    @Disabled
    @Test
    void startProject() {
        System.out.println("startProject");
        Mockito.when(programmer.program()).thenReturn("码字如飞");
        company.startProject();
    }
}

@ExtendWith(MockitoExtension.class)是JUnit5的一个扩展,使用了这个就可以直接使用@Mock去mock字段了。上面还出现了一个@InjectMocks,它的作用是将其修饰的类所依赖的mock的对象直接注入,例如

Company 依赖Programmer,我们通过@Mock mock了一个programmer对象,通过@InjectMocks 将programmer注入到了campony里。

然后我们使用了Mockito的when.thenReturn方法mock了programmer的program方法,使程序不至于真的去调用programmer的program方法。

关于Mockito的进一步学习,推荐Unit tests with Mockito - Tutorial

SpringBoot测试

SpringBoot 提供了一套可以启动Spring 容器的测试框架(@SpringBootTest),不到必要时候不要使用,太慢。不过当我们要测试Controlor时就需要这种方式了。

总结

虽然单元测试看起来比实际上有用,但仍然属于高级程序员应该掌握的知识。为什么大公司需要单元测试呢?因为参与项目的人员多,部门之间交流相当困难,还存在大量菜逼程序员,时不时会遇到狗屎一样的代码… 有了单元测试至少可以快速的发现你动了程序要死给你看的那个地方…。

暂时就说这么多吧,本文主要从宏观方面阐述了单元测试的一些方面… 等有精力整理一份具体的使用案例,不过意义不大,一则可以看官网,二则网上类似文章已经很多。

GitHub源码地址:springboot-learn

以上是关于Java单元测试总结的主要内容,如果未能解决你的问题,请参考以下文章

Java单元测试总结

Java 基础学习总结(201)—— 有效提高单元测试的 5 个编写技巧实践

Java 基础学习总结(201)—— 有效提高单元测试的 5 个编写技巧实践

Java 基础学习总结(201)—— 有效提高单元测试的 5 个编写技巧实践

单元测试 NPE,当我添加片段自定义转换时

Java Junit单元测试步骤总结