赞
踩
测试要求来源于阿里嵩山版Java开发手册:
定义:是指对软件中的最小可测试单元进行检查和验证。
Java里单元指一个方法。单元测试是在软件开发过程中要进行的最低级别的测试活动,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。
断言(Assertion)是测试中常用的一种技术,用于验证测试的预期结果是否与实际结果相符。断言通常用于测试框架中,用于检查代码的输出、行为或状态是否符合预期。在Java中,常见的断言工具是JUnit框架提供的断言方法,例如assertEquals()、assertTrue()、**assertFalse()**等。这些方法接受一个预期值和一个实际值,如果两者相符,则测试通过;否则,测试失败并抛出异常。
//1、assertEquals(expected, actual):验证预期值和实际值是否相等。
assertEquals(10, result); // 预期值为10,实际值为result
//2、assertTrue(condition):验证条件是否为真。
assertTrue(result > 0); // 验证result大于0
//3、assertFalse(condition):验证条件是否为假
assertFalse(result.isEmpty()); // 验证result不为空
//4、assertNull(object):验证对象是否为null
assertNull(result); // 验证result为null
//5、assertNotNull(object):验证对象是否不为null。
assertNotNull(result); // 验证result不为null
在Java中,也可以自定义断言来满足特定的测试需求。自定义断言可以根据你的应用程序逻辑进行定制化,以便更好地验证测试的预期结果。
//1、创建一个包含静态方法的类,用于执行自定义断言逻辑。
public class CustomAssertions {
public static void assertEvenNumber(int number) {
if (number % 2 != 0) {
throw new AssertionError("Expected an even number, but got: " + number);
}
}
}
//2、在测试代码中使用自定义断言方法。
@Test
public void testCustomAssertion() {
int result = 10;
CustomAssertions.assertEvenNumber(result);
}
//上述代码中自定义断言方法assertEvenNumber()用于验证给定的数字是否为偶数。如果数字不是偶数,断言会抛出AssertionError异常,测试将失败
Spring Boot Test框架是基于JUnit的测试框架,是用于编写和执行单元测试、集成测试和端到端测试的测试框架,专门针对Spring Boot应用程序进行测试。框架提供了一系列注解、类和工具,以简化测试的编写和执行过程,并提供了与Spring应用程序的集成和自动化配置
以下是Spring Boot Test框架的一些关键特点和功能:
// 基础依赖 spring-boot-starter-test中包含了junit和mockito等依赖
// pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
// gradle
testCompile 'org.springframework.boot:spring-boot-starter-test'
//还可以根据需要添加其他测试相关的依赖项,并确保它们与Spring Boot版本兼容
@SpringBootTest注解:
该注解用于标记测试类,指示Spring Boot在测试环境中加载完整的应用程序上下文,注解用于在Spring Boot的测试环境中加载完整的应用程序上下文。它会根据配置文件和注解自动配置应用程序的各个组件,并创建一个与实际运行环境相似的测试环境。通过加载完整的应用程序上下文,可以进行更全面的集成测试,包括对依赖关系的正确性和整个应用程序的功能进行验证
@RunWith:
该注解用于指定测试运行器,用于执行测试。在Spring Boot中通常使用**@RunWith(SpringRunner.class)**。
1. **BlockJUnit4ClassRunner**:默认的运行器,用于运行基于 JUnit 4 的测试类。
2. **PowerMockRunner**:用于支持使用 PowerMock 框架进行单元测试,可以模拟静态方法、私有方法等
3. **AndroidJUnit4**:用于在 Android 环境中运行测试,支持 Android 特定的功能和断言
4. **SpringRunner** 是 JUnit 4 的一个运行器(Runner),用于在 Spring 环境中运行测试,继承自 JUnit 的 **BlockJUnit4ClassRunner**,它提供了与 Spring 框架集成的功能。当使用 **SpringRunner** 运行测试类时,会自动创建和管理 Spring 容器,并将依赖注入到测试类中,使得可以在测试中使用 Spring 的功能,如依赖注入、事务管理、AOP 等
@MockBean: 该注解用于创建一个模拟对象(Mock)并将其注入到应用程序上下文中。它通常用于模拟依赖的外部组件或服务
@Autowired: 该注解用于自动注入依赖。它可以用于将被测试对象或其他组件注入到测试类中。
@Test:
该注解用于标记测试方法。测试方法应该使用该注解进行注释,以便在执行测试时被识别。@Test(timeout = 1000)
测试方法执行超过1000毫秒后算超时,测试将失败。@Test(expected = Exception.class)
测试方法期望得到的异常类,如果方法执行没有抛出指定的异常,则测试失败。
@Before:
该注解用于标记在每个测试方法之前执行的方法。它通常用于准备测试环境,例如初始化对象或设置测试数据
@After:
该注解用于标记在每个测试方法之后执行的方法。它通常用于清理测试环境,例如释放资源或重置状态
@RunWith(SpringRunner.class) // 使用Spring环境测试
@SpringBootTest //指示Spring Boot在测试环境中加载完整的应用程序上下文(整个应用程序的配置和运行环境)
public class Test {
@Autowired
private MyService myService;
@Test
public void testAddNumbers() {
int result = myService.addNumbers(2, 3);
assertEquals(5, result);
}
}
import net.remote.response.AccountApiResponse; import net.service.UserService; import net.site.SiteInfoService; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest @EnableAutoConfiguration public class SpBootMokDemo{ @Autowired private SiteInfoService siteInfoService; @MockBean private UserService userService; @Before public void setup() throws Exception { // 模拟方法调用的返回值 AccountApiResponse.AccountDetailReturn accountDetailReturn = new AccountApiResponse.AccountDetailReturn(); accountDetailReturn.setStatus("okk"); Mockito.when(userService.getUserInfoById("aaa")).thenReturn(accountDetailReturn); //模拟方法 参照PowerMock测试框架 } @Test public void testCase()throws Exception { System.out.println(userService.getUserInfoById("aaa")); } }
Mocking是软件开发中的一种技术,用于模拟或替代系统中的依赖项,以便进行单元测试。通过模拟依赖项的行为,我们可以控制测试环境并隔离被测代码的影响。在进行单元测试时,我们可以使用mocking框架创建和配置模拟对象,以替代实际的依赖项。通过定义模拟对象的行为和返回值,我们可以模拟依赖项的响应,以便在测试中验证被测代码的行为。
Mocking通常用于以下情况:
常见的mocking框架包括:
使用这些mocking框架,可以轻松地创建模拟对象,并使用断言和验证来验证被测代码与依赖项之间的交互。这样就可以更好地控制测试环境,减少外部因素对测试结果的影响,并提高测试的可靠性和可重复性
Mockito是一个用于Java的流行的mocking框架,用于进行单元测试和行为驱动开发(BDD)。它提供了简洁而强大的API,使开发人员可以轻松地创建和配置模拟对象,并验证被测代码与依赖项之间的交互。
// 1、添加Spring Boot Starter Test依赖,含了JUnit和Mockito的依赖会自动引入JUnit和Mockito无需单独添加它们的依赖 //在测试类中使用@RunWith(SpringRunner.class)注解,可与Spring Boot集成测试一起使用Mockito。可以使用Mockito来模拟Bean与注入依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> //2、引入Mockito的核心库 //这只是Mockito的核心依赖,如果需要使用Mockito的其他功能或扩展,可能需要添加其他相关依赖 <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>3.11.2</version> <scope>test</scope> </dependency> //除了Mockito本身的依赖,还需要确保项目中包含JUnit或其他适当的测试框架的依赖,因为Mockito通常与测试框架一起使用 //添加JUnit的依赖 , 引入整个 JUnit Jupiter 模块 <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.8.1</version> <scope>test</scope> </dependency>
Mockito.mock(classToMock) //模拟对象 可以使用@Mock注解代替 Mockito.verify(mock) //验证行为是否发生 Mockito.when(methodCall).thenReturn(value1).thenReturn(value2) //触发时第一次返回value1,第n次都返回value2 Mockito.doThrow(toBeThrown).when(mock).[method] //模拟抛出异常。 Mockito.mock(classToMock,defaultAnswer) //使用默认Answer模拟对象 Mockito.when(methodCall).thenReturn(value) //参数匹配 Mockito.doReturn(toBeReturned).when(mock).[method] //参数匹配(直接执行不判断) Mockito.when(methodCall).thenAnswer(answer)) //预期回调接口生成期望值 Mockito.doAnswer(answer).when(methodCall).[method] //预期回调接口生成期望值(直接执行不判断) Mockito.spy(Object) //用spy监控真实对象,设置真实对象行为 Mockito.doNothing().when(mock).[method] //不做任何返回 Mockito.doCallRealMethod().when(mock).[method] //等价于Mockito.when(mock.[method]).thenCallRealMethod(); 调用真实的方法 reset(mock) //重置mock //Mockito继承Matchers,anyInt()等均为Matchers方法,使用了参数匹配,那么所有的参数都必须通过matchers来匹配,当传入两个参数 Mockito.anyInt() //任何 int 值 ; Mockito.anyLong() //任何 long 值 ; Mockito.anyString() //任何 String 值 ; Mockito.anyBoolean() //任何boolean 值 Mockito.any(XXX.class) //任何 XXX 类型的值 等等 Mockito.any() //任何参数
//验证行为是否发生 List mock = Mockito.mock(List.class);//模拟创建一个List对象 mock.add(1);//调用mock对象的方法 mock.clear(); Mockito.verify(mock).add(1);//验证方法是否执行 //多次触发返回不同值 Iterator iterator = mock(Iterator.class);//mock一个Iterator类 Mockito.when(iterator.next()).thenReturn("hello").thenReturn("world");//预设当iterator调用next()时第一次返回hello,第n次都返回world String result = iterator.next() + " " + iterator.next() + " " + iterator.next();//使用mock的对象 Assert.assertEquals("hello world world",result);//验证结果 //模拟抛出异常 @Test(expected = IOException.class)//期望报IO异常 public void when_thenThrow() throws IOException{ OutputStream mock = Mockito.mock(OutputStream.class); Mockito.doThrow(new IOException()).when(mock).close();//预设当流关闭时抛出异常 mock.close(); } //参数匹配 @Test public void with_arguments(){ TestB b = Mockito.mock(TestB.class); Mockito.when(b.getSex(1)).thenReturn("男");//预设根据不同的参数返回不同的结果 Mockito.when(b.getSex(2)).thenReturn("女"); Assert.assertEquals("男", b.getSex(1)); Assert.assertEquals("女", b.getSex(2)); Assert.assertEquals(null, b.getSex(0));//对于没有预设的情况会返回默认值 } @Data class TestB{ private String name; public String getSex(Integer sex){ if(sex==1){ return "man"; }else{ return "woman"; } } } // 匹配参数 @Before public void setup() throws Exception{ // 模拟鉴权方法调用的返回值 : 输入任意参数 返回 true Mockito.when(accountService.isMain(Mockito.any())).thenReturn(true); // 模拟云盘剩余额度方法请求的返回值 : 输入任意参数 返回 10086 Mockito.when(rulesService.getAllowSSDVolumeSize(Mockito.any(),Mockito.any())).thenReturn(10086); // 模拟实例剩余额度方法请求的返回值 : 输入任意参数 返回 10086 Mockito.when(rulesService.getAllowCreateVmNum(Mockito.anyBoolean(),Mockito.any(),Mockito.any())).thenReturn(10086); } //重置 mock @Test public void reset_mock(){ List list = mock(List.class); Mockito. when(list.size()).thenReturn(10); list.add(1); Assert.assertEquals(10,list.size()); //重置mock,清除所有的互动和预设 Mockito.reset(list); Assert.assertEquals(0,list.size()); }
@Mock注解是Mockito框架提供的一个注解,用于创建一个模拟对象(Mock Object)。可以理解为对 mock 方法的一个替代。使用该注解时,要使用MockitoAnnotations.initMocks 方法,让注解生效。旧版的是initMocks,新版的是openMocks(从Mockito版本3.4.0开始引入的新方法)。也可以用MockitoJUnitRunner来代替MockitoAnnotations.initMocks
import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.Random; import static org.mockito.Mockito.*; public class MockitoDemo { @Mock private Random random; @Before public void before() { // 让注解生效 MockitoAnnotations.initMocks(this); } @Test public void test() { when(random.nextInt()).thenReturn(100); Assert.assertEquals(100, random.nextInt()); } } // 也可以用MockitoJUnitRunner来代替MockitoAnnotations.initMocks import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.util.Random; import static org.mockito.Mockito.*; @RunWith(MockitoJUnitRunner.class) public class MockitoDemo { @Mock private Random random; @Test public void test() { when(random.nextInt()).thenReturn(100); Assert.assertEquals(100, random.nextInt()); } }
使用**@Mock注解和Mockito.mock(classToMock)方法都可以创建模拟对象,它们的功能是相同的。它们都可以用于模拟依赖对象,以便进行单元测试时进行行为验证和结果验证。@MockBean注解是Spring Boot框架提供的注解,它是基于Mockito的@Mock注解的增强版。@MockBean注解除了创建模拟对象外,还会将模拟对象注入到Spring应用程序上下文中,替换实际的bean。这样,在进行集成测试时,可以模拟和控制应用程序中的依赖对象。
@Mock注解和Mockito.mock(classToMock)**之间存在的差异:
@Mock注解和Mockito.mock(classToMock)与@MockBean之间存在的差异:
总体而言,无论是使用**@Mock注解还是Mockito.mock(classToMock)方法,它们都是用于创建模拟对象的工具。选择使用哪种方式取决于个人喜好和项目的约定。@Mock注解与Mockito.mock(classToMock)用于基本的单元测试中创建模拟对象,而@MockBean**注解是Spring Boot提供的用于集成测试的注解,除了创建模拟对象外,还会自动将其注入到Spring上下文中。选择使用哪个注解取决于测试场景和需求。
PowerMock 是一个 Java 测试框架,它扩展了 Mockito 和 EasyMock,提供了更强大的能力,可以模拟和测试一些传统的难以测试的场景,如静态方法、私有方法、构造函数等。使用 PowerMock 可以帮助开发人员解决一些传统的难以测试的问题,提高代码的测试覆盖率和质量。在使用 PowerMock 进行测试时,通常需要结合 JUnit、Mockito 或 EasyMock 等测试框架来完成测试代码的编写。
PowerMock 的主要特点和功能包括:
// 引入依赖
testImplementation 'org.powermock:powermock-api-mockito2:2.0.9'
testImplementation 'org.powermock:powermock-module-junit4:2.0.9'
// 假设有一个包含静态方法的类 public class MyUtils { public static String generateUniqueId() { // 生成唯一标识符的逻辑 return UUID.randomUUID().toString(); } } // 使用 PowerMock 模拟静态方法 @RunWith(PowerMockRunner.class) @PrepareForTest(MyUtils.class) public class MyTestClass { @Test public void testGenerateUniqueId() { PowerMockito.mockStatic(MyUtils.class); Mockito.when(MyUtils.generateUniqueId()).thenReturn("mockedId"); String result = MyUtils.generateUniqueId(); assertEquals("mockedId", result); } }
// 假设有一个包含私有方法的类 public class MyClass { private String getPrivateData() { // 私有方法的逻辑 return "private data"; } public String processData() { String privateData = getPrivateData(); // 处理私有数据的逻辑 return "processed data: " + privateData; } } // 使用 PowerMock 模拟私有方法 @RunWith(PowerMockRunner.class) @PrepareForTest(MyClass.class) public class MyTestClass { @Test public void testProcessData() throws Exception { MyClass mockedInstance = PowerMockito.spy(new MyClass()); PowerMockito.when(mockedInstance, "getPrivateData").thenReturn("mocked private data"); String result = mockedInstance.processData(); assertEquals("processed data: mocked private data", result); } }
// 假设有一个需要模拟构造函数的类 public class MyObject { public MyObject() { // 构造函数的逻辑 } public String processData() { // 处理数据的逻辑 return "processed data"; } } // 使用 PowerMock 模拟构造函数 @RunWith(PowerMockRunner.class) @PrepareForTest(MyObject.class) public class MyTestClass { @Test public void testProcessData() throws Exception { MyObject mockedInstance = PowerMockito.mock(MyObject.class); PowerMockito.whenNew(MyObject.class).withNoArguments().thenReturn(mockedInstance); String result = mockedInstance.processData(); assertEquals("processed data", result); } }
//############### 模拟 final 类 // 假设有一个 final 类 public final class FinalClass { public String getData() { return "data"; } } // 使用 PowerMock 模拟 final 类 @RunWith(PowerMockRunner.class) @PrepareForTest(FinalClass.class) //使用 @PrepareForTest 注解来指定要模拟的类 public class MyTestClass { @Test public void testFinalClass() { FinalClass mockedInstance = PowerMockito.mock(FinalClass.class); Mockito.when(mockedInstance.getData()).thenReturn("mocked data"); String result = mockedInstance.getData(); assertEquals("mocked data", result); } } //############### 模拟 final 方法 // 假设有一个包含 final 方法的类 public class MyClass { public final String getData() { return "data"; } } // 使用 PowerMock 模拟 final 方法 @RunWith(PowerMockRunner.class) @PrepareForTest(FinalClass.class) //使用 @PrepareForTest 注解来指定要模拟的类 public class MyTestClass { @Test public void testFinalMethod() throws Exception { MyClass mockedInstance = PowerMockito.mock(MyClass.class); Mockito.when(mockedInstance.getData()).thenReturn("mocked data"); String result = mockedInstance.getData(); assertEquals("mocked data", result); } }
// 假设有一个包含静态初始化块的类 public class MyClass { static { // 静态初始化块的逻辑 } public String getData() { return "data"; } } // 使用 PowerMock 模拟静态初始化块 调用静态块时不做操作 @RunWith(PowerMockRunner.class) @PrepareForTest(MyClass.class)//使用 @PrepareForTest 注解来指定要模拟的类 public class MyTestClass { @Test public void testStaticInitializer() throws Exception { PowerMockito.mockStatic(MyClass.class); // PowerMock 提供的静态方法,用于模拟静态方法和静态初始化块 PowerMockito.doNothing().when(MyClass.class, "clinit");// PowerMock 的方法,用于指定当调用指定类的静态初始化块时执行的操作,doNothing() 方法表示在调用静态初始化块时不执行任何操作 MyClass mockedInstance = new MyClass(); String result = mockedInstance.getData(); assertEquals("data", result); } } // 使用 PowerMock 模拟静态初始化块 调用静态块时做操作 @RunWith(PowerMockRunner.class) @PrepareForTest(MyClass.class) public class MyTestClass { @Test public void testStaticInitializer() throws Exception { PowerMockito.mockStatic(MyClass.class); PowerMockito.doAnswer(invocation -> { //使用 PowerMock 的 doAnswer() 方法结合 Mockito 的 Answer 接口来模拟静态块操作 // 在静态初始化块调用时执行的操作 System.out.println("Performing custom operation during static initialization"); // 返回自定义的结果 return null; }).when(MyClass.class, "clinit"); MyClass mockedInstance = new MyClass(); String result = mockedInstance.getData(); assertEquals("data", result); } }
集成测试是它是一种测试方法,用于验证多个组件、模块或系统之间的协同工作。它主要关注不同部分之间的交互和集成,以确保整个系统按预期工作。
集成测试的目的是验证不同组件之间的接口、数据传递和协作是否正常。它可以帮助发现系统集成方面的问题,确保系统的整体功能和性能达到预期。集成测试通常在单元测试之后进行,以确保单元测试通过后的组件在集成时能够正常工作。
一般的集成测试流程:
import net.remote.response.AccountApiResponse; import net.service.UserService; import net.site.SiteInfoService; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest @EnableAutoConfiguration public class SpBootMokDemo{ @Autowired private SiteInfoService siteInfoService; @MockBean private UserService userService; @Before public void setup() throws Exception { // 模拟方法调用的返回值 AccountApiResponse.AccountDetailReturn accountDetailReturn = new AccountApiResponse.AccountDetailReturn(); accountDetailReturn.setStatus("okk"); Mockito.when(userService.getUserInfoById("aaa")).thenReturn(accountDetailReturn); } @Test public void testCase()throws Exception { System.out.println(userService.getUserInfoById("aaa")); } }
在springboot集成测试中需要使用@RunWith(SpringRunner.class)创建和使用spring容器,加载应用上下文。所以在测试时如果有静态方法的外部依赖就无法使用PowerMock做到有效模拟(由于PowerMock需要使用到@RunWith(PowerMockRunner.class)用于支持使用 PowerMock 框架进行单元测试,可以模拟静态方法、私有方法等)。(经过调研暂未找到可以同时使用SpringBootTest框架进行针对springboot应用程序进行测试的同时模拟静态方法依赖的技术手段,需后续调研)
推荐在开发业务功能时,避免使用静态方法调用外部环境。可以代替为普通public方法,并将该类交由spring容器管理。
交由spring容器管理的类都可以使用@MockBean:(该注解用于创建一个模拟对象(Mock)并将其注入到应用程序上下文中。它通常用于模拟依赖的外部组件或服务)进行模拟
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。