9.单元测试【白盒测试】
- JUnit:Java语言编写的开源的回归测试框架
相关概念 | 解释 |
---|---|
测试 | 被 @Test 注解定义的测试方法,为了运行这个方法,JUnit会创建一个包含类的新实例,然后在调用这个被注释的方法。 |
测试类 | 包含多个 @Test 注释方法的容器。 |
断言 | Assert 断言,为了进行验证,使用由JUnit的Assert类提供的assert方法。常用的assert方法: 1. assertEquals() :用来查看对象中存放的值是否是期望值2. assertFalse() 和assertTrue() :用来查看变量是否为false或true3. assertSame() 和assertNotSame() :用来比较两个对象的引用是否相等和不相等4. assertNull() 和assertNotNull() :用来查看对象是否为空和不为空 |
测试集 | Suite 是包含不同测试的容器,将多个相关的测试类归为一组,一起运行。@RunWith(Suite.class) @SuiteClasses({Test1.class, Test2.class}) public void TestSuitMain{ //将Test1和Test2中的测试用例均执行一遍 } |
测试运行器 | Runner 管理测试类的生命周期,用来运行测试。JUnit4是支持向后兼容的。JUnit 自带的几个测试运行器都继承了 org.junit.runner.Runner 类。 |
JUnit 3.x 版本通过测试方法的命名(test+方法名)来确定是否进行测试,并且所有的测试类都必须继承TestCase
。JUnit 4.x 版本全面引入了注解来执行测试用例。BeforeClass
、AfterClass
、After
、Before
、Test
、Ignore
。其中,BeforeClass
和 AfterClass
在每个类加载开始和结束时运行,需要设置静态 static
方法。Before
和 After
需要在每个测试方法开始之前和结束之后运行。
@BeforeClass
public static void beforeClassTest() {
System.out.println("单元测试开始之前执行初始化【静态方法】");
}
@Before
public void beforeTest() {
System.out.println("单元测试开始之前执行");
}
@Test
public void test() {
System.out.println("单元测试执行");
}
@After
public void afterTest() {
System.out.println("单元测试结束之后执行");
}
@AfterClass
public static void afterClassTest() {
System.out.println("单元测试结束之后执行【静态方法】");
}
注:
1. JUnit在执行每个@Test
方法之前,会为测试类创建一个新的实例。保证测试方法之间的独立性,避免在测试代码中产生意外的副作用。
2.JUnit的入口函数:JUnitCore.class中包含main方法。[org.junit.runner]
-
Spring Boot单元测试
Spring Boot 提供了一些使用程序和注解,用来帮助我们测试应用程序。测试由两个模块支持:
spring-boot-test[核心项目]
和spring-boot-test-autoconfigure[自动配置测试]
开发时通常引入
spring-boot-starter-test
依赖:<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency>
该依赖提供了以下测试库:
测试库 | 简介 |
---|---|
JUnit | 标准的单元测试Java应用程序 |
Spring Test & Spring Boot Test | 针对Spring Boot应用的单元测试 |
Mockito | Java Mocking框架,用于模拟任何Spring管理的Bean |
AssertJ | 流畅的assertion库,提供更多期望值与返回值的方法比较方式 |
Hamcrest | 库的匹配对象 |
JSONassert | 对JSON对象或者JSON字符串断言的库 |
JsonPath | 类似于XPath在xml文档中的定位,JsonPath表达式通常是用来路径检索或设置Json的。 |
Spring Boot测试脚手架举例:Spring Boot使用一系列注解来增强单元测试,其基本模板如下:
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestClass {
...
}
@RunWith
是JUnit标准注解,设置单元测试框架不使用内置默认方式进行,而是使用 @RunWith
注解指明的类提供单元测试,所有的 Spring 单元测试总是使用 SpringRunner.class
。
@SpringBootTest
注解用于 Spring Boot 应用测试,该注解会根据包名逐级往上找,一直找到 Spring Boot 主程序 [通过 @SpringBootApplication
来判断是否是主程序],并在单元测试的时候启动该类创建 Spring 上下文环境。
Spring 单元测试并不会在每个单元测试方法前都启动一个全新的 Spring 上下文,Spring 单元测试会缓存上下文环境,提供给每个单元测试方法。如果单元测试方法改变了上下文 [更改了Bean定义],此时就需要在单元测试方法添加 @DirtiesContext
注解以提示 Spring 重新加载 Spring 上下文。
测试场景举例:
-
Service
常规的Service层测试是通过 Controller层 调用 Service层进行测试,这里需要注意几点:
- 单元测试需要保证可重复测试,因此希望Service测试完毕后,数据可以自动回滚。
- Service 依赖的其他未开发完毕模块的时候,测试该怎样进行?
- 大多数 Service 对接的是数据层,如何模拟数据测试场景?
上述问题解决方案如下:
- Spring Boot单元测试默认会在单元测试方法运行结束后进行
事务回滚
。 - 既然依赖了未完成[不存在]模块,那么我们将采用上述
Mockito
库 来模拟注入未完成对象。 - Spring 引入了
@sql
注解,在测试之前执行一系列的SQL脚本进行数据初始化工作。
举栗子:
@RunWith(SpringRunner.class) @SpringBootTest @Transactional public class UserServiceTest { @Autowired UserService userService; @MockBean private CreditSystemService creditSystemService; @Test public void testService() { int userId = 10; int expectedCredit = 100; given(this.creditSystemService.getUserCredit(anyInt())).willReturn(expectedCredit); // 测试 UserService 的 getCredit 获取用户积分 int credit = userService.getCredit(10); // 断言方法判断返回值与期望值 assertEquals(expectedCredit, credit); } }
贴上UserService接口实现类代码:
@Service @Transactional public class UserServiceImpl implements UserService { @Autowired CreditSystemService creditSystemService; @Autowired UserDao userDao; @Override public int getCredit(int userId) { User user = userDao.single(userId); if(user != null) { return creditSystemService.getUserCredit(userId); } else { return -1; } } }
由于单元测试不能实际调用 creditSystemService,因此,在单元测试类中使用了
@MockBean
注解。@MockBean private CreditSystemService creditSystemService;
@MockBean
注解,可以自动注入 Spring 管理的 Service [Bean],用来提供模式实现。因此这里的 creditSystemService 对象实际上并不是 CreditSystemServiceImpl 的实现实例,而是一个通过 Mockito工具创建的 CreditSystemService M o c k i t o M o c k MockitoMock MockitoMockxxxxxx 实例。given
方法是 Mockito 的一个静态方法,用来模拟一个Service方法调用返回,anyInt()
表示可以传入任何参数,willReturn()
方法说明该调用将返回其方法参数值。given(this.creditSystemService.getUserCredit(anyInt())).willReturn(expectedCredit);
上述语句表示:使用模拟对象 creditSystemService 调用 getUserCredit() 方法,无论传入参数如何,都将返回 expectedCredit 的值。
注:默认情况下,单元测试完毕,事务会进行回滚。有时需要查看数据库测试结果而不希望事务回滚。可以在方法上使用
@Rollback(false)
即可。 -
Controller
Spring Boot可以单独测试 Controller 代码,用来验证与 Controller 相关的 URL路径映射、文件上传、参数绑定、参数校验等特性。以上特性均通过
@WebMvcTest
来完成单元测试。@RunWith(SpringRunner.class) @WebMvcTest(UserController.class) public class UserControllerTest { @Autowired private MockMvc mvc; @MockBean UserService userService; @Test public void testMVC() throws Exception { int userId = 10; int expectedCredit = 100; //模拟userService given(this.userService.getCredit(anyInt())).willReturn(100); //http 调用 mvc.perform(get("/user/{id}", userId)).andExpect(content().string(String.valueOf(expectedCredit))); } }
@WebMvcTest
表示这是一个MVC测试,其参数可以传入多个待测试的 Controller 类。MockMvc
是 Spring 提供的专门用于测试的 Spring MVC 类。@MockBean
用来模拟UserController调用的所有Service。[Spring MVC 测试中,Spring容器并不会初始化@Service
、@Component
注解的类]perform
表示完成一次MVC调用,Spring MVC Test 是 Servlet 容器内的一种模拟测试,实际上并不会发起正在的 HTTP 调用。get()
方法模拟了一次 get 请求,参数站位符 { } 对应 userId。andExpect()
表示请求期望的返回结果。 -
请求模拟
关于 Spring 专门提供的用于测试的 Spring MVC 类:MockMvc,其核心方法为:perform
public ResultActions perform(RequestBuilder requestBuilder)
RequestBuilder 类可以通过使用 MockMvcRequestBuilders 的 get、post、Multipart等方法进行实现请求模拟。以下是常用的例子:
模拟请求类型 | 代码实现 |
---|---|
模拟get请求 | mvc.perform(get("/test/{id}", 77)); |
模拟post请求 | mvc.perform(post("/test/{id}", 77)); |
模拟提交复选框内容 | mvc.perform(get("/test/{id}", 77).param(“job”, “IT”, “gov”)); |
模拟提交键值对参数 | mvc.perform(get("/test/{id}", 77).param(“msg”, “hello world”)); |
模拟Session和Cookie | mvc.perform(get("/user.html").sessionAttr(name, value)); mvc.perform(get("/user.html").cookie(new Cookie(name, value)) |
模拟提交Json | String json = … ; mvc.perform(get("/user.html").content(json)); |
模拟提交HTTP Header | mvc.perform(get("/test/{id}", 77). contentType(“application/x-www-form-urlencoded”). accept(“application/json”). header(header, value)); |
-
请求结果返回比较
通过上述描述我们知道:mockMvc 该类的核心方法 perform 返回值为 ResultActions 实例。该实例代表了 MVC 模拟请求调用的结果,并提供一系列的
addExpect()
方法来对结果进行比较。mvc.perform(get("/user/{id}", userId)) .andExpect(content().string(String.valueOf(expectedCredit)));
上述代码中表示:期望返回的内容为与设定值相符。