欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

轻松入门:Junit在Java单元测试中的详细指南

最编程 2024-08-05 19:27:15
...

单元测试机制及术语

软件开发V形模式

说到单元测试,就不得不提到软件工程中的软件开发模式-V形模式。V模型是对瀑布模型的修正,强调了验证活动,由Paul Rook在1980年率先提出。在瀑布模型中,由于早期的错误可能要等到开发后期的测试阶段才能发现,所以可能带来严重的后果。V模型就是在这点上改进了瀑布模型,即在软件开发的生存期中,开发活动和测试活动几乎同时开始,这两个并行的动态的过程就会极大地减小bug和error出现的概率。V模型是瀑布模型的变种,它反映了测试活动与分析和设计的关系。
所以开发在代码编写过程中,单元测试就是不可缺少的一环。然而在现在各个开发团队都采用敏捷开发的模式中,100%代码和分支覆盖率的单元测试需要付出的代价太大,不适合敏捷开发模式。所以在实际开发过程中会简化单测要求,一般只要求代码和分支覆盖率在80%左右。
在这里插入图片描述

单元测试机制

  • 测试按照阶段分为单元测试,结合测试,系统测试
  • 单元测试只测试标的对象
  • 通过打桩,模拟标的对象的所有对象引用及方法调用,实现标的对象的代码和分支覆盖
  • 单元测试只考虑测试对象本身逻辑,其他引用对象或调用方法的逻辑都不在考虑范围内

单元测试机制示意图。
单元测试机制示意图

单元测试术语

  • 模拟:模拟测试对象引用其他对象
  • 打桩:当调用模拟的对象中方法时,返回指定的值
  • 执行:执行测试对象的测试方法
  • 断言:验证测试方法的返回值是否和预期一致

Junit引入

引入依赖

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

在测试方法上引入注解

@RunWith(MockitoJUnitRunner.class)
@SpringBootTest

RunWith注解指明了单测使用什么方式启动。如果只是测单个类的逻辑,将其他类对象的引用全部打桩模拟返回,则可以使用MockitoJUnitRunner.class。这种方式启动迅速,效率高,但是无法使用Spring工程中的环境配置参数。
如果是希望单测启动后,依旧使用工程的各种配置信息,并且不只是测当前类方法,而是测这个类方法为入口的整个逻辑,则可以使用@RunWith(SpringRunner.class)作为启动方式。

单元测试常用注解

  • @BeforeClass:标注在方法上,该方法全局只会执行一次,而且是第一个运行。
    当我们运行几个有关联的用例时,可能会在数据准备或其它前期准备中执行一些相同的命令,这个时候为了让代码更清晰,更少冗余,可以将公用的部分提取出来,放在一个方法里,并为这个方法注解@BeforeClass。意思是在测试类里所有用例运行之前,运行一次这个方法。例如创建数据库连接、读取文件等。必须是public static void
  • @Before:标注在方法上,在测试方法执行前执行。
    在每个用例运行之前都运行一次。主要用于一些独立于用例之间的准备工作。
    比如两个用例都需要读取数据库里的用户A信息,但第一个用例会删除这个用户A,而第二个用例需要修改用户A。那么可以用@BeforeClass创建数据库连接。用@Before来插入一条用户A信息。
    须是public void,不能为static。不止运行一次,根据用例数而定。
  • @Test:标注测试方法。标记测试方法,Test类都必须继承一个基类。
    注意:测试方法必须是public void,即公共、无返回数据。可以抛出异常。
  • @After:在测试方法运行之后运行。用法和Before相对
  • @AfterClass:标注在方法上,该方法全局只会执行一次,而且是最后一个运行。
    跟@BeforeClass对应,在测试类里所有用例运行之后,运行一次。用于处理一些测试后续工作,例如清理数据,恢复现场。
    必须是public static void,即公开、静态、无返回。这个方法只会运行一次。
  • @Ignore:和@Test相对应。被Ignore标记的方法,不会被执行。
    有时候我们想暂时不运行某些测试方法\测试类,可以在方法前加上这个注解。在运行结果中,junit会统计忽略的用例数,来提醒你。

下面都是基于第一种,对单个类对象的单元测试为例。

常规方法测试

首先对测试对象类中所有注入对对象进行打桩。
假设我的测试对象类只引用了一个TestA对象

public class AppCommonServiceImplTest extends HermesApplicationTests {

    // 打桩测试对象的注入对象
    @Mock
    private TestA testA;

    // 注入测试对象
    @InjectMocks
    private TestServiceImpl testServiceImpl;

    /**
    * 测试方法,一般使用要测试的方法名后面加Test来组成测试方法名
    */
    @Test
    @SuppressWarnings("unchecked")
    public void queryAddTruckTypeListTest(){

        // 模拟一个返回参数
        List<Person> retList = new ArrayList<>();
        Person testVo = new SysDictionary();
        testVo.setAge(23);
        testVo.setSex("1");
        testVo.setName("测试");
        retList.add(testVo);
 
        // 打桩测试方法调用的外部方法。
        // 语义是,当打桩的实例sysDictionaryMapper的selectList方法
        // 以任何LambdaQueryWrapper实例为入参调用时,返回模拟的方法       
        Mockito.when(sysDictionaryMapper.selectList(Mockito.any(LambdaQueryWrapper.class))).thenReturn(retList);
        ResultVo resultVo = testServiceImpl.testMethod();

        // 打印返回结果
        LOGGER.info("queryAddServiceTypeListTest result: {}", JsonUtils.obj2json(resultVo));
        

        // 断言,即验证返回结果是否正确。
        // 例如我假设我的测试方法是把查询到的人,将年龄拼接在名字后面返回
        // 验证结果是否正确
        Assert.assert(resultVo.getName(),"测试(23)");

    }

以上就是一个简单的单元测试方法。省略了测试对象和测试方法的代码部分。

特殊类及方法测试

PowerMockito打桩静态方法

假设测试的对象方法中含有静态工具类的使用,这个时候,就需要用到PowerMockito来对静态方法进行打桩。
在这里插入图片描述
然后和常规方法一样的方式,对静态引用进行打桩。

// 此处打桩,当date工具类调用format转换日期格式时,抛出运行时异常
PowerMockito.when(DateUtils.format(Mockito.any(Date.class)))
.thenThrow(new RuntimeException);

打桩没有返回值的方法(void方法)

使用Mockito的doNothing和doThrow方法

Mockito.doNothing().when(mock).method(args);       //不做任何处理
Mockito.doThrow(toBeThrown).when(mock).method(args);     //抛错

例如

Mockito.doNothing().when(userServiceImpl)
.updateUserInfo(Mockito.any(User.class));

关于测试覆盖率

运行测试方法

点击方法前面的小图标,就可以执行测试方法
在这里插入图片描述

  • Run:直接运行
  • Debug:使用调式方式运行
  • Run ‘method()’ with Coverage:运行完测试用例后计算覆盖率

查看测试覆盖率

方法执行完后,可以在coverage窗口查看代码覆盖率。

在这里插入图片描述

如果没有自动加载出覆盖率,则需要自己制定计算覆盖率的范围。
如下图调出配置页面,或者直接点击上图中的Edit链接
在这里插入图片描述

在配置中,添加测试对象作为覆盖率计算的范围。
在这里插入图片描述
然后重新运行测试用例
在这里插入图片描述
三个指标分别时class覆盖率,方法覆盖率,行覆盖率。
因为计算范围只有一个类,覆盖了一个类,所以类覆盖率时100%。18个方法覆盖了8个,130行代码覆盖了40行。

查看测试覆盖情况

打开测试对象类,可以通过行号上的颜色来快速定位到已经覆盖到的行和未覆盖的行。
在这里插入图片描述

  • 绿色部分为已覆盖代码
  • 红色部分为未覆盖代码
  • 左侧红色棱形为未覆盖分支
  • 黄色棱形为部分覆盖分支
  • 绿色棱形为全覆盖分支

常见问题分析

关于多次返回的方法打桩

Mockito.when(mockitoClass.method(Mockito.anyString()))
.thenReturn(aList).thenReturn(bList).thenReturn(null)

同一个打桩方法下,可以添加多个thenReturn,按照次序在循环调用中依次返回指定值。若调用次数大于thenReturn的指定次数,则超出的都返回最后指定的值。

带参数的Test注解

①设置超时时间,避免死循环不能终止
@Test(timeout=100)
public void infinity() { while(true); }

②声明报错
@Test(expected = Exception.class)
public void testDivide() throws Exception { cal.divide(1, 0); }

执行测试方法报错

执行方法时,报如下错误
在这里插入图片描述
解决方法如下,勾选argline复选框
在这里插入图片描述

以上就是博主整理的关于单测的一些问题,如果有不解或者不正之处,还望指出。

推荐阅读