spring boot框架开发入门实战(三)单元测试

单元测试

原则

单元测试的原则:AIR、BCDE、FIRST、3R、3A、SOCKS、Right-BICEP(右臂二头肌) (baidu.com)

AIR原则

A:Automatic(自动化)全自动执行,并且非交互式的,输出结果无需人工检查(禁止使用system.out打印来人工判断),而是通过断言验证。

I:Independent(独立性)分层测试,单元测试用例之间决不能互相调用,也不能依赖执行的先后次序。

R:Repeatable(可重复)可重复执行,不受外部环境( 网络、服务、中间件等)影响。

BCDE 原则

B: Border,边界值测试,包括循环边界、特殊取值、特殊时间点、数据顺序等。

C: Correct,正确的输入,并得到预期的结果。

D: Design,与设计文档相结合,来编写单元测试。

E : Error,单元测试的目标是证明程序有错,而不是程序无错。为了发现代码中潜在的错误, 我们需要在编写测试用例时有一些强制的错误输入(如非法数据、异常流程、非业务允许输入等)来得到预期的错误结果。

FIRST原则

F:Fast(快速),测试如果跑得不够快,就不会让人想常常跑,不常跑的测试最后也就失去的它意义了。所以实务上会使用 mock 工具,mock 其他依赖的物件或环境,来加速测试的执行。

I:Independent(独立),测试要相互独立,一个测试不会依赖其他测试,如果互相依赖的话,一个测试的失败会影响其他测试也跟着失败,那么在找问题点的时候将会变得更困难。

R:Repeatable(可重复),测试应该要可以在任何环境中重复执行。减少因环境因素而产生测试失败的问题。

S:Self-Validating(自我验证),测试程式应该要输出布林值。不管是测试成功或失败。简单来说就是可以在测试报告很清楚的看到红灯(测试失败)或绿灯(测试成功)。

T:Timely(及时),撰写测试要及时,最好是在写产品程式前先写(TDD的概念)。

测试边界原则 - CORRECT
  • Conformance - 一致性 (值是否符合预期的格式?)
  • Ordering - 有序性 (一组值的顺序是否符合预期?)
  • Range - 区间性 (值是否在一个合理的最大值和最小值的范围内?)
  • Reference - 引用性 / 耦合性 (代码是否引用了一些不受其直接控制的外部因素,由这些外部因素所引入的前置条件或后置条件所造成的影响是否符合预期?)
  • Existence - 存在性 (值是否存在(例如:非 null,非零,存在于某个集合中等)? )
  • Cardinality - 基数性 : 计数性 (是否恰好有足够的值?(重点关注 0-1-n 原则,问题往往发生在这三个边界上。))
  • Time - 时间性(绝对时间及相对时间)- (所有事情是否按顺序发生?是否在正确的时间?是否及时?)
3R 原则

Responsible: 谁开发谁负责测试,在哪里开发就在哪里测试。

Reliable: 测试 case 要可靠,并且是值得信赖的,对于底层的任何改动都要能够及时感知。

Repeative: 所有单元测试用例都要能够重复运行。能够重复运行就能够进行回归测试、覆盖率统计等等。

实战
依赖
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
  <scope>test</scope>
</dependency>
测试mapper

创建测试代码文件:

package com.jx.uam.mapper;

import com.jx.uam.bean.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import static org.junit.jupiter.api.Assertions.assertTrue;

@SpringBootTest
public class UserMapperTest {

    @Autowired
    UserMapper userMapper;

    @Test
    void testUserMapper() {
        User user = userMapper.queryUserByID(1);
        System.out.println("用户信息:");
        System.out.println(user);
        assertTrue(user.getId()==1);
    }
}

mvn clean test,可以看到输出

用户信息:
User(id=1, username=jinxiang, department=IND, valid=1)
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.488 s - in com.jx.uam.mapper.UserMapperTest

如果增加 assertTrue(user.getId()==2);

[ERROR] Failures: 
[ERROR]   UserMapperTest.testUserMapper:24 expected: <true> but was: <false>
[INFO]
[ERROR] Tests run: 2, Failures: 1, Errors: 0, Skipped: 0
[INFO]
[ERROR] There are test failures.

直接测试controller
package com.jx.uam.controller;

import com.jx.uam.controller.UserController;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;


@SpringBootTest
@AutoConfigureMockMvc
public class UserControllerTest {
    @Autowired
    private MockMvc mockMvc;

    @Test
    public void queryUserByIDTest() throws Exception {
        mockMvc.perform(get("/user/queryUserByID?userId=1").accept(MediaType.APPLICATION_JSON_UTF8))
                .andExpect(status().isOk())
                .andDo(MockMvcResultHandlers.print());
    }

}
模拟数据测试controller
package com.jx.uam.controller;

import com.jx.uam.bean.User;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest
@AutoConfigureMockMvc
public class UserControllerMockTest {
    @Autowired
    private MockMvc mockMvc;
    @MockBean
    UserController userController;


    @Test
    public void queryUserByIDMockTest() throws Exception {
        // mock虚拟数据对象,始终返回固定内容
        User user = new User(1, "xiaoming", "IND", 1);
        when(userController.queryUserByID(anyInt())).thenReturn(user);
        mockMvc.perform(get("/user/queryUserByID?userId=1").accept(MediaType.APPLICATION_JSON_UTF8))
                .andExpect(status().isOk())
                .andDo(MockMvcResultHandlers.print());
    }

}
问题
BeforeAll只能注解静态方法

@BeforeAll method ‘public void com.jx.uam.controller.UserControllerTest.setUp() throws java.lang.Exception’ must be static

原因:在Junit5中,BeforeAll注解用于静态方法。

解决:

改用BeforeEach,或者把方法改为static

单元测试报错

Request processing failed; nested exception is java.lang.NullPointerException

原因:这种报错原因很多,主要是某一个变量为空

https://blog.csdn.net/syc000666/article/details/105998091/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值