大量实例助攻,让你的单元测试更高效

导读

       单元测试作为程序员的必修课,对代码的稳定性起着关键性的作用,但是你真的会写单元测试么?什么才算是真正的单元测试?这些疑问你都将在文章中得到解答。

       在本文中,我们将主要基于Mockito框架来介绍如何编写单元测试,必要时使用PowerMock来对一些Mockito无法处理的方法进行操作,并且伴随有大量实例以助于理解。

1 什么是单元测试

什么是单元测试?我们先看看维基百科中对其的定义:

       在计算机编程中,单元测试(英语:Unit Testing)又称为模块测试,是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。

       在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。

根据以上定义,简单来说Java中单元测试就是对类中方法进行测试的工作。

2 单元测试的意义

2.1 为什么要进行单元测试

       单元测试是软件测试的基础,不仅会直接影响到软件的后期测试,在很大程度上还会影响产品的最终质量。而且能写出高质量的单元测试代码,也是程序猿的基本修养之一。

● 提高代码质量,写过单元测试的代码,在提测、联调时bug数会明显减少,修改代码时也会对代码也会更有信心;

● 减少调试时间,如果认真的做好了单元测试,在系统集成联调时非常顺利,减少查找、调试bug时反复编译的时间成本;

● 在单元测试时某些问题就很容易发现,而在后期的测试中发现问题所花的成本将成倍数上升。

● 为代码重构保驾护航,可以更放心的去重构代码;

● 通过单元测试快速熟悉代码,比如代码做什么工作,有哪些特殊情况需要考虑,包含哪些业务。

2.2 什么时候写单元测试

编写单元测试的时机无非以下三种:

● 代码实现之前(TDD提倡);

● 与代码实现同步进行,开始写之前想好细节用例,然后一个个实现,代码一步步完善;

● 代码完成之后再写单元测试,效果不如前2种,而且单元测试的难度和工作量可能随着代码质量的变化成倍增加。

       很多人觉得单元测试是在代码完成之后编写的,其实这种想法是错误的,根据TDD(Test-driven development)思想,倡导先写测试程序,然后再具体实现其功能。

       但是很多人可能没有达到这种水平,此时退而求其次,边写代码边完成单元测试不妨为一种折中的选择。

2.3 哪些情况需要写单元测试

那么单元测试的粒度需要划分多细,哪些情况才需要写单元测试呢?以下四点可供参考:

● 涉及大量计算;

● 公共代码、工具类;

● 逻辑复杂、容易出错、不易理解;

● 核心业务代码。

3 单元测试的方法

在Sprig Boot环境下进行单元测试,首先需要在pom.xml中添加包依赖:

1<dependency>
2    <groupId>org.springframework.boot</groupId>
3    <artifactId>spring-boot-starter-test</artifactId>
4    <scope>test</scope>
5</dependency>

Spring Boot提供的spring-boot-starter-test启动器集成了常用的测试类库:

       Spring Boot中单元测试类放在src/test/java目录下,通过IDEA可以自动创建测试类,快捷键为==⇧⌘T==(MAC)或者==Ctrl+Shift+T==(Window)。

还可以通过添加Coverage插件分析测试覆盖率。

3.1 Mock介绍

       在写单元测试的过程中,一个很普遍的问题是,要测试的类可能会有很多的依赖,而这些依赖的类、对象又会有别的依赖,从而形成一个大的依赖树,要在单元测试的环境中完整地构建这样的依赖,是一件很困难的事情。

       基于以上问题,我们引入了Mock方法,对被测试类所依赖的其他类和对象进行mock——构建它们的一个假对象,并定义这些假对象的行为,然后提供给被测试对象使用。

       被测试对象像使用真的对象一样使用它们,这样我们就可以把测试的目标限定于被测试对象本身,从而实现依赖的隔离。

       简单来说,mock对象就是在调试期间用来作为真实对象的替代品;mock测试就是在测试过程中,对那些不容易构建的对象用一个虚拟对象来代替测试的方法。

3.2 使用的框架

       Mockito是一个针对Java的单元测试模拟框架,是为了简化单元测试过程中测试上下文(或者称之为测试驱动函数以及桩函数)的搭建而开发的工具。

3.3 测试流程

1@RunWith(MockitoJUnitRunner.class)

       在被测试类之前加以上注解,表示使用指定运行器来运行测试类。MockitoJUnitRunner不需加载其他spring bean,也不需要启动spring的那一整套东西,启动速度非常快。

3.3.1 创建mock对象

对被测类中@Autowired的对象,用@Mock标注;

对被测类自己,用@InjectMocks标注。

被@Mock标注的对象会自动注入到被@InjectMocks标注的对象中。

@Spy可以实现部分mock,即不打桩时默认会执行真实的方法,如果打桩则返回桩实现。下面是官方给出的一个示例:

 1   List list = new LinkedList();
 2   List spy = spy(list);
 3
 4   //optionally, you can stub out some methods:
 5   when(spy.size()).thenReturn(100);
 6
 7   //using the spy calls real methods
 8   spy.add("one");
 9   spy.add("two");
10
11   //prints "one" - 这个函数还是真实的
12   System.out.println(spy.get(0));
13
14   //100 is printed - size()函数被替换了
15   System.out.println(spy.size());

3.3.2 设置测试桩

      也叫打桩,就是定制mock对象的具体行为,通过它可以指定某个类的某个方法在什么情况下返回什么样的值。

org.mockito.Mockito:

● when(...).thenReturn(...)

● doReturn(...). when(class).method(...)

       大多情况下,上面2种方法通用,建议优先使用when-thenReturn,因为其可读性较高且会检查回传值的类型(Type-Safe Check):

1        List<String> list = mock(List.class);
2        when(list.get(100)).thenReturn("33");   //返回字符串,正常
3        when(list.get(0)).thenReturn(33);     //返回数字,IDE报错,进行了type-safety的检查
4        doReturn(33).when(list).get(0); //IDE没报错,没进行type-safety的检查

       并且对于Spy对象,when-thenReturn会真实调用方法,只是返回时返回设定的值,而doReturn根本不会调用实际的方法:

1    public boolean exist(String telOrigin) {
2        int i = 1/0;
3        return Optional.ofNullable(mapper.selectByTelOrigin(telOrigin)).isPresent();
4    }
 1    @Test
 2    @Rollback
 3    public void save() {
 4        BusinessAgent businessAgent = new BusinessAgent();
 5        businessAgent.setTelOrigin("13888888888");
 6        businessAgent.setAgentId(1);
 7        doReturn(true).when(businessAgentRepository).exist("13888888888");
 8//        when(businessAgentRepository.exist("13888888888")).thenReturn(true);
 9        int agentId = businessAgentRepository.save(businessAgent);
10        verify(businessAgentExtMapper).updateByPrimaryKeySelective(businessAgent);
11        verify(businessAgentExtMapper, never()).insert(businessAgent);
12        assertThat(agentId, equalTo(businessAgent.getAgentId()));
13        ……
14    }

       比如上述代码,对于被测试类中存在1/0的非法算数运算,运行时汇报异常,使用when-thenReturn时测试无法通过,报算数异常,但是doReturn时测试可以正常通过,说明其根本没有调用实际方法。

org.mockito.BDDMockito:

● given(...).willReturn(...)

更符合BDD开发的习惯,Given…When…Then…实际上就是设定场景的状态、适用的事件,以及场景的执行结果,如:

1given(businessHousePic.getHouseId()).willReturn(3L);

3.3.3 调用方法

调用被测对象的方法,获取返回值。

3.3.4 验证结果

对有返回值的方法,验证返回数据是否和期望匹配。这里用到了2个方法verify()和assertThat():

1/**验证businessHousePicRepository调用了方法invalid
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值