单元测试知识
百度百科
单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义,如C语言中单元指一个函数,Java里单元指一个类,图形化的软件中可以指一个窗口或一个菜单等。总的来说,单元就是人为规定的最小的被测功能模块。单元测试是在软件开发过程中要进行的最低级别的测试活动,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。
POM依赖
SpringBoot 中的 pom.xml 文件需要添加的依赖
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
@runWith注解作用
注解 | 描述 | @RunWith | 就是一个运行器 | @RunWith(JUnit4.class) | 就是指用JUnit4来运行 | @RunWith(SpringJUnit4ClassRunner.class) | 让测试运行于Spring测试环 境,以便在测试开始的时候自动创建Spring的应用上下文 | @RunWith(SpringRunner.class) | SpringRunner extends SpringJUnit4ClassRunner | @RunWith(Suite.class) | 就是一套测试集合 | @RunWith(MockitoJUnitRunner.class) | 使Mockito的注解生效 |
单元测试注解
注解 | 描述 | @Test | 使用该注解标注的public void方法会表示为一个测试方法 | @BeforeClass | 表示在类中的任意public static void方法执行之前执行 | @AfterClass | 表示在类中的任意public static void方法之后执行 | @Before | 表示在任意使用@Test注解标注的public void方法执行之前执行 | @After | 表示在任意使用@Test注解标注的public void方法执行之后执行 |
@SpringBootTest 注解作用
属性 | 描述 | Class<?>[] classes() | 指定启动类 | SpringBootTest.WebEnvironment webEnvironment() | 经常和测试类中@LocalServerPort 一起在注入属性时使用。会随机生成一个端口号 |
@RunWith(JUnit4.class)使用
?定义计算类Calculate
import lombok.extern.slf4j.Slf4j;
/**
* 计算类定义
*
* @author yangyanping
* @date 2022-04-07
*/
@Slf4j
public class Calculate {
/**
* 加法
*/
public int add(int a, int b) {
log.info("{} add {}", a, b);
return a + b;
}
/**
* 减法
*/
public int sub(int a, int b) {
log.info("{} sub {}", a, b);
return a - b;
}
/**
* 乘法
*/
public int mult(int a, int b) {
log.info("{} mult {}", a, b);
return a * b;
}
/**
* 除法
*/
public int dev(int a, int b) {
log.info("{} dev {}", a, b);
return a / b;
}
}
@Slf4j
public class InjectCalculate {
@Autowired
private Calculate mockCalculate;
public int add(int a, int b) {
log.info("{} add {}", a, b);
return mockCalculate.add(a, b);
}
}
定义单元测试类CalculateTest
/**
* 计算类单元测试类定义
*
* @author yangyanping
* @date 2022-04-07
*/
@RunWith(JUnit4.class)
public class CalculateTest {
private Calculate calculate;
private int a;
private int b;
/**
* 初始化方法
*/
@Before
public void init() {
calculate = new Calculate();
a = 10;
b = 2;
}
/**
* 加法
*/
@Test
public void testAdd() {
Assert.assertEquals(12L, calculate.add(a, b));
}
/**
* 减法
*/
@Test
public void testSub() {
Assert.assertEquals(8L, calculate.sub(a, b));
}
/**
* 乘法
*/
@Test
public void testMult() {
Assert.assertEquals(20L, calculate.mult(a, b));
}
/**
* 除法
*/
@Test
public void testDev() {
Assert.assertEquals(5L, calculate.dev(a, b));
}
/**
* 除法
*/
@Test(expected = ArithmeticException.class)
public void testDevException() {
calculate.dev(a, 0);
}
}
运行效果
Mock单元测试
Mock的概念
所谓的mock就是创建一个类的虚假的对象,在测试环境中,用来替换掉真实的对象,以达到两大目的:
- 验证这个对象的某些方法的调用情况,调用了多少次,参数是什么等等
- 指定这个对象的某些方法的行为,返回特定的值,或者是执行特定的动作
Mockito的注解生效
方式一:给被测类添加@RunWith(MockitoJUnitRunner.class)注解
/**
* 计算类单元测试类定义
*
* @author yangyanping
* @date 2022-04-07
*/
@Slf4j
@RunWith(MockitoJUnitRunner.class)
public class MockCalculateTest {
}
方式二:在初始化方法中使用MockitoAnnotations.openMocks(MockCalculateTest.class)
/**
* 初始化方法
*/
@BeforeClass
public static void init() {
MockitoAnnotations.openMocks(MockCalculateTest.class);
}
@Mock 和 @Spy
注解 | 描述 | @Mock | 对函数的调用均执行mock(即虚假函数),不执行真正部分 | @Spy | 对函数的调用均执行真正部分。Mockito中的Mock和Spy都可用于拦截那些尚未实现或不期望被真实调用的对象和方法,并为其设置自定义行为。二者的区别在于Mock不真实调用,Spy会真实调用。 | @InjectMocks | 通过创建一个实例,它可以调用真实代码的方法,其余用@Mock(或@Spy)注解创建的mock将被注入到用该实例中 |
MockCalculateTest类定义
/**
* 计算类单元测试类定义
*
* @author yangyanping
* @date 2022-04-07
*/
@Slf4j
@RunWith(MockitoJUnitRunner.class)
public class MockCalculateTest {
@Mock
private static Calculate mockCalculate;
@Spy
private static Calculate spyCalculate;
@InjectMocks
private InjectCalculate injectCalculate;
private int a = 10;
private int b = 2;
/**
* 初始化方法
*/
@BeforeClass
public static void init() {
MockitoAnnotations.openMocks(MockCalculateTest.class);
}
/**
* 加法
*/
@Test
public void testAdd() {
// 默认返回结果是返回类型int的默认值
Assert.assertEquals(0, mockCalculate.add(a, b));
}
/**
* 减法
*/
@Test
public void testSub() {
// 默认会走真实方法
Assert.assertEquals(8L, spyCalculate.sub(a, b));
//打桩后,不会走了
when(spyCalculate.sub(2, 1)).thenReturn(8);
Assert.assertEquals(8L, spyCalculate.sub(2, 1));
// 但是参数不匹配的调用,依然走真实方法
Assert.assertEquals(1L, spyCalculate.sub(4, 3));
}
/**
* 加法
*/
@Test
public void testInjectAdd() {
when(mockCalculate.add(anyInt(), anyInt())).thenReturn(10);
// 默认返回结果是返回类型int的默认值
Assert.assertEquals(10, injectCalculate.add(2, 3));
}
}
运行testAdd
结果没有日志:10?add 2 输出,即没有执行Calculate 类真实的add方法,
运行testSub
输出结果有运行日志,即执行了Calculate 类真实的sub方法
@RunWith(SpringRunner.class)
定义抽象基类
/**
* 单元测试基础类
*
* @author yangyanping
* @date 2022-04-07
*/
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest(classes = WebConfig.class)
public abstract class AbstractBootTest {
@Before
public void init() {
log.info("Spring boot test is being started, please wait ....");
}
}
WebConfig定义
/**
* 系统配置类
* @author yangyanping
* @date 2021-08-25
*/
@EnableAsync
@EnableCaching
@MapperScan("com.flow.dao")
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ImportResource("classpath:applicationContext-web.xml")
@SpringBootApplication(scanBasePackages = {"com.flow"}, exclude = {DataSourceAutoConfiguration.class})
public class WebConfig {
}
@Slf4j
public class FlowQueryProviderTest extends AbstractBootTest {
@Autowired
private FlowQueryProvider flowQueryProvider;
@Test
public void testQueryApproveNode(){
ApproveNodeQuery query = new ApproveNodeQuery();
query.setApproveErp("xiyangyang");
query.setApproveStatus(FlowReqNodeStatus.ADOPT.getCode());
FlowResult<PageResult<FlowReqNodeVo>> flowResult =
flowQueryProvider.queryApproveNode(query);
Assert.assertNotNull(flowResult.getModel());
}
}
|