IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 开发测试 -> Java云同桌学习系列(二十五)——单元测试 -> 正文阅读

[开发测试]Java云同桌学习系列(二十五)——单元测试

Java云同桌系列(二十五)——单元测试

为什么要做单元测试?

  • 检测特定的、明确的、细颗粒的功能
  • 单元测试不仅仅保证当前代码的正确性,还用来保证代码修复改进重构之后的正确性

1. Mockito简介

Mockito是当前主流的单元测试Mock框架,主要用于对一些不易构造外部依赖强的对象采用虚拟的方式在封闭的环境中进行测试。

官方文档:http://site.mockito.org/mockito/docs/current/org/mockito/Mockito.html

2. 原理浅谈

Mockito本质上是应用了Proxy代理模式,在真实对象调用之前,经过代理对象,进行判断决定相应的处理。一般使用该框架的目的,都是为了使用代理对象返回一个预设的返回值。

更底层的研究,之所以能返回预设的返回值,是因为保存被代理方法的返回值、参数名、入参信息等等,通过使用了一种名为“Stub“的方式设置了返回值,该方式通常被称之为”打桩“,可以理解为满足形式要求但没有实现实际功能的占坑/代理代码,不会影响原有流程。

3. Mockito使用详解

3.1 初始化对象

初始化对象更推荐注解的方式,方便快捷

  • mock 对象

    使用@Mock修饰需要mock的对象,对于使用Dubbo框架的其他服务的对象,还需要使用@DubboReference修饰后才可以成功mock

  • 真实对象

    使用@Spy修饰需要真实调用其方法的对象

  • 注入对象(通常是被测试对象)

    使用@InjectMocks修饰对象,会将 @Mock、@Spy对象自动注入进去

  • 运行器 @Runwith

    不同于SpringBootTest使用SpringRunner.class,mockito需要使用MockitoJUnitRunner.class

初始化代码示例:

@RunWith(MockitoJUnitRunner.class)
public class MockDemoMockitoTest {
    //mock对象
    @Mock
    UserService userService;
    //真实对象
//    @Spy

    @InjectMocks
    //被测试对象,@mock与 @spy 的对象会自动注入进去
    MockDemo mockDemo;


    @Before
    public void before() throws Exception {
        //每次单元测试执行前执行此逻辑
    }

    @After
    public void after() throws Exception {
        //每次单元测试执行后执行此逻辑
    }

    @Test
    public void mockitoTest() {
        //given 测试预设
        
        //when 执行操作
        

        //then 结果断言
        

    }
}

MockIto官方较为推荐测试驱动开发的格式编写单元测试,即使用//given //when //then 注释为测试用法基石

3.2 Mock方法

  • 有返回值方法

    最常用的便是When(被mock的方法.(预设参数)).thenReturn(预设的返回值)方式

    //mock 逻辑
    //当执行指定方法,且参数为指定参数时,返回指定的值
    Mockito.when(userService.getOneByIdCard("111")).thenReturn(new User());
    

    被mock的原逻辑

    User user = userService.getOneByIdCard(idCard);
    

? 指定参数时,也可以忽略,使用any()作为参数,表示任意参数都可以返回指定的值

  • 无返回值方法

    通过情况该种Mock比较少见,不可以用上一种方法,因为When()不可以传入返回值为void 的方法,需要使用doAnswer(预设逻辑).when(mock对象).mock方法(预设参数)

    Mockito.doAnswer(new Answer() {
                @Override
                public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
                    //获取方法执行时的第0个参数
                    User user = (User)invocationOnMock.getArgument(0);
                    user.setNickName("修改无返回值的方法内部逻辑");
                    return null;
                }
            }).when(userService).updateById(any());
    

    被mock的原逻辑

    //无返回值的方法
    userService.updateById(user);
    
  • mock 抛出异常

    mock 某个方法抛出异常来测试某些异常处理逻辑doThrow(mock抛出的异常).when(mock对象).mock方法(预设参数);

    Mockito.doThrow(new RuntimeException()).when(userService).updateById(any());
    

3.3 断言

有关断言的工具类比较多,此处只简单列两类个人比较常用的断言,更多断言请读者自行百度

  • 断言值

    //断言值
    Assert.assertEquals("修改无返回值的方法内部逻辑",user.getNickName());
    
  • 断言执行次数

    //断言指定方法的任意参数,被执行指定次数
    Mockito.verify(userService,Mockito.times(1)).getOneByIdCard(any());
    

3.4 前置后置处理

使用@Before@After修饰方法,可以在所有测试方法执行之前,执行之后执行特定的代码

@Before
public void before() {
//所有测试方法执行前执行
}

@After
public void after(){
//所有测试方法执行后执行
}

4. 完整示例代码

被测试类

@Service
public class MockDemo {
    @Autowired
    UserService userService;

    public User mockitoTest(String idCard){
        //有返回值的方法
        User user = userService.getOneByIdCard(idCard);
        //无返回值的方法
        userService.updateById(user);
        return user;
    }
}

测试类

@RunWith(MockitoJUnitRunner.class)
public class MockDemoMockitoTest {
    //mock对象
    @Mock
    UserService userService;
    //真实对象
	//@Spy

    @InjectMocks
    //被测试对象,@mock与 @spy 的对象会自动注入进去
    MockDemo mockDemo;


    @Before
    public void before() throws Exception {
        //每次单元测试执行前执行此逻辑
    }

    @After
    public void after() throws Exception {
        //每次单元测试执行后执行此逻辑
    }

    @Test
    public void mockitoTest() {
        //given 测试预设
        Mockito.when(userService.getOneByIdCard("111")).thenReturn(new User());

        Mockito.doAnswer(new Answer() {
            @Override
            public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
                //获取方法执行时的第0个参数
                User user = (User)invocationOnMock.getArgument(0);
                //预设逻辑
                user.setNickName("修改无返回值的方法内部逻辑");
                return null;
            }
        }).when(userService).updateById(any());

        //mock 抛出异常
		//Mockito.doThrow(new RuntimeException()).when(userService).updateById(any());

        //when 执行操作
        User user = mockDemo.mockitoTest("111");

        //then 结果断言
        Assert.assertEquals("修改无返回值的方法内部逻辑",user.getNickName());
        //断言执行次数
        Mockito.verify(userService,Mockito.times(1)).getOneByIdCard(any());

    }

我们完整的梳理一遍

  1. 测试类中调用执行mockitoTest()并传入参数为111
  2. 然后执行getOneByIdCard()时,我们有mock的逻辑,参数也是和mock逻辑要求的参数一致,然后该方法按照mock逻辑返回一个新的User对象
  3. 然后执行到无返回值的updateById()方法,该方法也在测试类中被mock,我们在该mock逻辑中,先获取到传入updateById方法的参数User对象,然后将其NickName属性修改为固定字符串,然后结束
  4. 至此,主方法运行完毕,回到测试方法的断言部分
  5. 首先断言返回的User对象的NickName属性是否是我们mock设置的一个固定字符串
  6. 然后断言,getOneByIdCard()是否执行了一次
  7. 测试类全部执行完毕,如果断言全部正确,则不会报任何问题,然后任一断言不通过,则会报ComparisonFailure

5. PowerMock

PowerMock是基础单元测试框架的升级版,提供了一些更加强大的mock功能。相比Mockito框架,PowerMock解决了Mockito不能mock私有方法、静态方法的缺陷

5.1 Maven依赖

? 不同于Mockito已经集成到Springboot依赖中,PowerMock需要单独添加依赖,并且依赖版本与Mockito依赖版本有一些限制,;两个依赖对应的版本对应关系建议百度,此处列出我当前使用无问题的版本

	<dependency>
      <groupId>org.powermock</groupId>
      <artifactId>powermock-module-junit4</artifactId>
      <version>1.7.4</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.powermock</groupId>
      <artifactId>powermock-api-mockito2</artifactId>
      <version>1.7.4</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.mockito</groupId>
      <artifactId>mockito-core</artifactId>
      <version>2.8.9</version>
      <scope>test</scope>
    </dependency>

5.2 mock方法

相比于Mockito,PowerMock的用法基本保持一致,只是提供了更多种的mock手段

powerMock测试类一般需要加如下两个注解

//运行器 选择PowerMockRunner
@RunWith(PowerMockRunner.class)
//修饰被测试的方法(若mock私有方法时加此注解)
@PrepareForTest(MockDemo.class)
  • mock静态方法

    只需要在调用when前使用MockStatic方法

    //mock静态方法
    PowerMockito.mockStatic(MockDemo.class);
    PowerMockito.when(mockDemo.updateNickName(any())).thenReturn(user.setNickName("嘿嘿,静态方法被我Mock了"));
    
  • mock 私有方法

    被mock的方法

     public String powerMockPrivateTest(){
            //private method
            return getString();
        }
    

    mock逻辑,特别注意此处需要方法调用对象,是Spy真实对象,而非mock对象

    MockDemo spy = PowerMockito.spy(new MockDemo());
    PowerMockito.when(spy,"getString").thenReturn("private method mock return");
    

5.3 完整示例代码

被测试方法

public class MockDemo {
    @Autowired
    UserService userService;
	
	public User powerMockTest(String idCard){
        User user = userService.getOneByIdCard(idCard);
        //静态方法
        user = MockDemo.updateNickName(user);
        return user;
    }

    public String powerMockPrivateTest(){
        //private method
        return getString();
    }

    private String getString(){
        return "private method return";
    }

    static User updateNickName(User user){
        return user.setNickName("静态方法修改");
    }
}

测试方法

@RunWith(PowerMockRunner.class)
@PrepareForTest(MockDemo.class)
public class MockDemoPowerMockTest {
    //mock对象
    @Mock
    UserService userService;
    //真实对象
//    @Spy

    @InjectMocks
    //被测试对象,@mock与 @spy 的对象会自动注入进去
    MockDemo mockDemo;


    @Before
    public void before() throws Exception {
        //每次单元测试执行前执行此逻辑
    }

    @After
    public void after() throws Exception {
        //每次单元测试执行后执行此逻辑
    }

    @Test
    public void powerMockTest() throws Exception {
        //given 测试预设
        User user = new User();
        Mockito.when(userService.getOneByIdCard(any())).thenReturn(user);

        //mock静态方法
        PowerMockito.mockStatic(MockDemo.class);
        PowerMockito.when(mockDemo.updateNickName(any())).thenReturn(user.setNickName("嘿嘿,静态方法被我Mock了"));

        //when
        User result = mockDemo.powerMockTest("");

        //then
        //断言执行了Mock的静态方法,昵称为指定mock字符串
        Assert.assertEquals("嘿嘿,静态方法被我Mock了",result.getNickName());
    }

    @Test
    public void powerMockPrivateTest() throws Exception {
        MockDemo spy = PowerMockito.spy(new MockDemo());
        PowerMockito.when(spy,"getString").thenReturn("private method mock return");

        String s = spy.powerMockPrivateTest();

        //assert this return string from mock
        Assert.assertEquals("private method mock return",s);

    }

6. 延伸

  • SquareTest插件推荐

    对于编写大量的单元测试时,可以使用SquareTest插件,帮助生成结构代码,也就是测试类的一些常用注解,when结构代码,需要的对象也都会按默认值赋值,省去大量时间编写这些结构代码,可能更加专注于测试主要逻辑

  • IDEA单元测试覆盖率

    很多时候需要注意某个类的单元测试覆盖率,此时可以点击执行单元测试类的绿色按钮,第一种是run,第二种是debug,第三种就是统计覆盖率了

    此时回到被测试类,会被看到绿色色块的就是被测试覆盖的代码,红色就是测试未覆盖,切到左侧的project侧边栏,还可以看到该类的覆盖百分比

  开发测试 最新文章
pytest系列——allure之生成测试报告(Wind
某大厂软件测试岗一面笔试题+二面问答题面试
iperf 学习笔记
关于Python中使用selenium八大定位方法
【软件测试】为什么提升不了?8年测试总结再
软件测试复习
PHP笔记-Smarty模板引擎的使用
C++Test使用入门
【Java】单元测试
Net core 3.x 获取客户端地址
上一篇文章      下一篇文章      查看所有文章
加:2022-04-18 18:12:40  更:2022-04-18 18:13:08 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/17 22:26:37-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码