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 小米 华为 单反 装机 图拉丁
 
   -> 开发测试 -> Mockito结合Junit单测使用 -> 正文阅读

[开发测试]Mockito结合Junit单测使用

Mockito

前言

文章为个人总结存在一些问题还请大佬指教。

简介

是一个单元测试的模拟框架。通过设置模拟类达到模拟数据测试的轻框架。

依赖

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-all</artifactId>
    <version>1.9.5</version>
    <scope>test</scope>
</dependency>

如何编写一个好的测试代码?

1.测试代码应该保持和生产代码紧凑

2.测试代码各个依赖不应该强耦合,一些外部链接可以模拟

3.覆盖各种情况的出现

4.不应该模拟自己不拥有的类,例:第三方的代码

5.不应该模拟全部,否则不能达到测试真实代码的目的

Mockito的局限性

2.x版本(java 6+)

1.不能模拟构造方法

2.不能模拟静态方法

1.x版本(java 5+)

1.不能模拟final类

2.不能模拟final方法

3.不能模拟构造方法

4.不能模拟静态方法

快速入门

真实代码

@RestController
public class UserController {

    @Resource
    private UserService userService;
    
    public User insertUser(User user){
        ...
    }

测试代码

@RunWith(MockitoJUnitRunner.class)		//设置运行环境
public class UserControllerTest {

    @InjectMocks	//被测对象
    private UserController controller;
    @Mock		//注入模拟对象
    private UserService userService;

    @Test		//方法测试
    public void insertUser() throws Exception {
        User user = new User();
        user.setId(5);
        user.setUsername("张三");
        user.setAge(18);
        user.setPassword("567");
        user.setTime(new Date());
        //表示当userService执行insert()方法时什么都不做
        doNothing().when(userService).insert(user);
        controller.insertUser(user);
    }
}

设置运行Mockito测试环境方式

1.@RunWith(MockitoJUnitRunner.class) 缺点在于当测试代码需要其他环境则无法进行

2.MockitoAnnotations.initMocks(this); 通常在@Before方法中指定,该方式可用于多个运行环境中测试

3.@Rule MockitoRule rule = MockitoJUnit.rule();

注入Mock对象

什么是mock对象

mock(模拟)对象,调用该对象的任何方法不会走真实方法逻辑,调用结果都相当于什么都不做,需自己通过stub来设置执行逻辑。

可以解决测试中不需要关心的/耦合强的/难以真实调用的对象方法,我们可以为它设置模拟数据返回。

例:1.代码中需要调用远程服务,而这里的逻辑不应该属于该单元测试中的范围,就可以模拟该远程服务对象返回模拟数据,保证正常测试执行。
2.又或是该测试代码我们不需要关心sql的执行情况,就可以模拟dao层对象返回测试数据

注入方式

1.@Mock

表明被注解对象是一个Mock(模拟)对象

2.mock(Class class);

UserService service = mock(UserService.class);

注入Spy对象

什么是spy对象

spy(监视)对象,与mock对象不同的是调用该对象会执行真实对象的代码逻辑,需自己通过stub来设置模拟逻辑。

注入方式

1.@Spy

表明被注解对象是一个Mock(模拟)对象

2.spy(Class class);

UserService service = spy(UserService.class);

@InjectMocks对象

会将其他的@Mock/@Spy修饰的对象注入到该注解修饰的对象。
此外使用该注解必须设置运行环境,例:MockitoAnnotations.initMocks(this);

注入逻辑

Mockito 将尝试仅通过构造函数注入、setter 注入或属性注入依次注入模拟。 如果以下任一策略失败,则 Mockito不会报告失败; 即必须自己提供依赖项。

构造函数注入;

选择最大的构造函数,然后使用仅在测试中声明的模拟来解析参数。 如果使用构造函数成功创建了对象,则Mockito 不会尝试其他策略。 Mockito 决定不破坏具有参数构造函数的对象。
注意:如果找不到参数,则传递 null。 如果需要不可模拟的类型,则不会发生构造函数注入。 在这些情况下,您必须自己满足依赖项。

属性设置器注入

mocks 将首先按类型解析,然后,如果有多个相同类型的属性,则通过属性名称和模拟名称的匹配。
注意1:如果你有相同类型的属性,最好用匹配的属性命名所有@Mock注解的字段,否则Mockito可能会混淆并且不会发生注入。
注意2:如果@InjectMocks 实例之前没有初始化并且有一个无参数构造函数,那么它将用这个构造函数初始化。

mock()/spy()注入

mocks 将首先按类型解析,然后,如果有多个相同类型的属性,则通过字段名称和模拟名称的匹配。
注意1:如果你有相同类型的字段,最好用匹配的字段命名所有@Mock注释的字段,否则Mockito可能会混淆并且不会发生注入。
注意2:如果@InjectMocks 实例之前没有初始化并且有一个无参数构造函数,那么它将用这个构造函数初始化。

多层级mock依赖注入解决

在日常开发中经常会出现这种情况:controllerA 注入了 serviceA,serviceA 注入了 daoA;

这种情况下如果我们想要在controllerA调用方法,然后当执行到daoA方法时进行模拟,mockito是无法完成的;

Mockito不支持为一个spy/mock注入另一个spy/mock。

解决

目前知道的解决方法仅有一个,有其他解决的方法希望大佬不吝赐教。

可以利用反射将serviceA的daoA属性换成mock的daoA属性。

@RunWith(MockitoJUnitRunner.class)		//设置运行环境
public class UserControllerTest {

    @InjectMocks	//被测对象
    private UserController controller;
    @Spy		//注入模拟对象
    private UserService userService;
	@Mock
    private UserMapper userMapper;
    
    @Before
    public void setup(){
        ReflectionTestUtils.setField(userService,"userMapper", this.userMapper);
    }
    
    @Test		//方法测试
    public void insertUser() throws Exception {
        User user = new User();
        user.setId(5);
        user.setUsername("张三");
        user.setAge(18);
        user.setPassword("567");
        user.setTime(new Date());
        //表示当userService执行insert()方法时什么都不做
        doNothing().when(userMapper).insert(user);
        controller.insertUser(user);
    }
}

Stub设置预设数据/逻辑

stub是为了让对象去执行的预设的一串逻辑,提供模拟的数据。

when(T methodCall)

表示当…,需要有参数,所以里面不能执行无返回值方法

thenReturn | Answer | Throw() | CallRealMethod

通常在when()后使用:when(list.get(0)).thenReturn(5); 表示当list执行了get(0)就返回5;

四个依次表示:然后返回 | 自定义逻辑返回 | 抛出个异常 | 回调真实方法

thenAnswer介绍

//使用自定义答案存根模拟的示例:
//Answer<T>:T返回值类型,Answer表示一个可获得Mock对象的接口
when(mock.someMethod(anyString())).thenAnswer(
    new Answer() {
        public Object answer(InvocationOnMock invocation) {
            Object[] args = invocation.getArguments();
            Object mock = invocation.getMock();
            return "called with arguments: " + Arrays.toString(args);
        }
    });

//stream流式编程
when(mock.someMethod(anyString())).thenAnswer(x->{
    Object[] args = invocation.getArguments();
    Object mock = invocation.getMock();
    return "called with arguments: " + Arrays.toString(args);
})

doReturn | Answer | Throw() | CallRealMethod

与thenXXX() 区别:

1.执行链路不一样
  doReturn(5).when(list.get(0));
  when(list.get(0)).thenReturn(5);
  
2.doXXX()可以作用于无返回值的方法
  doReturn(5).when(list).add(5);	//不报错
  when(list.add(5)).thenReturn(5);	//编译报错

3.对于spy对象建议使用doXXX(),因为spy对象执行方法是真实方法逻辑,使用when()会真实调用该方法而产生不希望出现的错误,如空指针异常等;
  而我们通常使用stub就是为了预设数据不走真实方法,doXXX就是这样的逻辑。

参数匹配

any():匹配任何参数;anyInt():匹配任意int参数 …

when(list.get(anyInt())).doReturn(5);

Mock controller层请求进行测试

@SpringBootTest
@RunWith(SpringRunner.class)
public class CustomerOrderControllerTest {

    @Autowired
    private CustomerOrderController controller;

    private MockMvc mockMvc;
    
    private WebApplicationContext applicationContext;


    @Before
    public void setupMockMvc(){
        //参数指定controller则指对该controller获取其相关上下文
        //mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
        //读取整个web上下文构造Mock
        mockMvc = MockMvcBuilders.webAppContextSetup(applicationContext).build();
    }

    @Test
    public void exceptionOrderBack() throws Exception {
        String json = "{}";
        mockMvc.perform(MockMvcRequestBuilders.post("/v1/customercenter/customerOrder/exceptionOrderBack")
                        .contentType(MediaType.APPLICATION_JSON)
                        .accept(MediaType.APPLICATION_JSON)
                        .content(json.getBytes())
                       ).andExpect(MockMvcResultMatchers.status().isOk());
    }

Springboot中进行JUnit Mockito单测

依赖

<!-- 该依赖中整合了mockito -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

快速入门

@SpringBootTest
@RunWith(SpringRunner.class)
public class UserControllerTest {

    @Autowired
    private UserController controller;
    @SpyBean
    private UserService userService;
    @MockBean
    private UserMapper userMapper;
    
    @Before
    public void setupMockMvc(){
        ReflectionTestUtils.setField(userService,"userMapper", this.userMapper);
    }
    
    @Test
    public void insertUser() throws Exception {
        when(userMapper.getUserById(1l)).thenReturn(new User());
        User user1 = controller.getUser(1l);
        //不建议这种打印自己观察的方式进行单元测试,应采用Assert断言方式,这样能一目了然让我们知道测试是否通过
        System.out.println(user1);
    }
}

不同于Mockito本身,springboot整合后注入Mock对象采用的是 @SpyBean 和 @MockBean

区别是该注解是为了运行环境在SpringRunner/SpringJUnit4ClassRunner下的。

被@SpyBean 和 @MockBean注解的对象会在初始化时将原对象替换为该Mock对象,从而达到Mock的效果;但依旧存在多层级依赖注入问题,依旧采用反射设置属性的方式解决。

原因猜想

@SpyBean 和 @MockBean仅是替换了原来在Spring上下文的Bean,而这样其实经mock的serviceA中的daoA属性与替换的mock的daoA是没有关联的(相当于有两个对象,mockd的serviceA的daoA属性指向的并不是替换掉的mock(daoA))。

此外本人目前不清楚@SpyBeans和@MockBeans的使用,有了解者还希望能在评论区告知。

结尾

如果大家在使用中遇到过一些问题,欢迎一起讨论。

  开发测试 最新文章
pytest系列——allure之生成测试报告(Wind
某大厂软件测试岗一面笔试题+二面问答题面试
iperf 学习笔记
关于Python中使用selenium八大定位方法
【软件测试】为什么提升不了?8年测试总结再
软件测试复习
PHP笔记-Smarty模板引擎的使用
C++Test使用入门
【Java】单元测试
Net core 3.x 获取客户端地址
上一篇文章      下一篇文章      查看所有文章
加:2021-11-09 19:53:42  更:2021-11-09 19:56:24 
 
开发: 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/18 2:54:00-

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