目录
相关知识
单元测试覆盖率
粗粒度覆盖率
细粒度覆盖率
单元测试框架、工具
JUnit4相关注解
断言Assert
JUnit使用示例
准备工作
Controller测试示例一
Controller测试示例二
单元测试时的事务回滚
相关知识:
- 单元测试是编写的一小段代码,用于检验目标代码的一个很小的、很明确的功能模块是否正确。
- 简单地讲,单元测试,就是对代码功能是否达到预期要求的测试。
- 单元测试:方便,效率高,不必启动繁重的服务等。
单元测试覆盖率:
粗粒度覆盖率:
粗粒度的覆盖包括类覆盖和方法覆盖两种:
- 类覆盖是指类中只要有方法或变量被测试用例调用或执行到,那么久说这个类被测试覆盖了。
- 方法覆盖是指只要在测试用例执行过程中,某个方法被调用了,则无论执行了该方法中的多少行代码,都可以认为该方法被覆盖了。
细粒度覆盖率:
- 行覆盖(Line Coverage)
行覆盖也称为了语句覆盖,用来度量可执行的语句是否被执行到。
行覆盖率 = 执行到了的语句行数 / 总的可执行行数。
- 分支覆盖(Branch Coverage)
分支覆盖也称为判定覆盖,用来度量程序中每一个分支是否都被执行到。
分支覆盖率 = 执行到了的分支数 / 分支总数。
- 条件判定覆盖(Condition Decision Coverage)
条件判定覆盖要求设计足够的测试用例,能够让判定中每个条件的所有可能结果情况至少被执行一次。
条件判定覆盖率 = 被执行到了的结果数 / 条件的所有结果数。
注:如果是JUnit5的话,我们可以只编写一个测试用例上,然后在这个测试用例上借助于
@ParameterizedTest注解和@CsvSource注解来定义多次运行时的参数列表,来完成
条件判定覆盖测试用例的编写。如果是JUnit4的话,就需要编写多个测试用例来完成
条件判定覆盖测试用例的编写了。
注:如果表达式exp有三种可能结果A、B、C,若exp结果为A则执行分支a;若exp结果为
B则执行分支b;若exp结果为C也执行分支b。
条件判定覆盖就是要穷举exp的所有结果
(这里为A、B、C);而分支覆盖就是要穷举exp(的所有结果引起)的所有分支(这里为a、b)。
- 条件组合覆盖(Multiple Condition Coverage)
如果说条件判定覆盖注重的是结果,那么条件组合覆盖注重的就是过程了。示例说明:boolean类型的数exp的值由三个boolean类型的数exp1、exp2、exp3决定(如:exp = exp1 && exp2 || exp3),条件判定覆盖只需要覆盖exp的所有结果即可(1或者0);而条件组合覆盖就需要覆盖exp1、exp2、exp3的所有组合结果(0、0、0或者0、0、1或者0、1、0或者0、1、1或者1、0、0或者1、1、0或者1、80、1或者1、1、1)。所以,对于一个包含了n个条件的判定,至少需要个测试用例才可以。
条件组合覆盖率 = 执行到了的条件组合数 / 条件总组合数。
注:如果表达式exp(注:表达式可能非常复杂,如 exp = exp1 && exp2 || exp3)成立,那么执行分
支a;如果表达式不成立,那么执行分支b。
条件判定覆盖就是要穷举所有exp的可能;而
分支覆盖就是要穷举所有分支。
- 路径覆盖(Path Coverage)
路径覆盖要求能测试到程序中所有可能的路径。我们知道A && B中,若A为false时,就不需要在走B了,就能直接得到A && B的结果为false;而A || B中,若A为true时,就不需要在走B了,就能直接得到A || B的结果为true。
如果说条件组合覆盖是不考虑程序走向的无脑穷举,那么路径覆盖就是考虑了程序走向的穷举。如:
exp1 && exp2 || exp3中,大体路线分为:
①走exp1和exp2,不走exp3。
此情况中,要走的有exp1和exp2,当且仅且exp1和exp2均为true时,才满足此情况,所以
有(exp1 == true, exp2 == 1);
②走exp1和exp2,还要走exp3。
此情况中,要走的有exp1、exp2还有exp3,当且仅且exp1为true,exp2为false时,才会走
exp3,而无论exp3为true还是为false,都满足 此情况。所以有
(exp1 == true, exp2 == false, exp3 == false)和
(exp1 == true, exp2 == false, exp3 == true)。
③走exp1,还要走exp3。
此情况中,要走的有只有exp1和exp3,当且仅且exp1为false时,才会只走exp1和exp3,而
无论exp3为true还是为false,都满足 此情况。所以有(exp1 == false, exp3 == false)和
(exp1 == false, exp3 == true)。
路径覆盖率 = 执行到了的路径数 / 可执行的路径总数。
注:不同公司对单元测试要求不一样;有的公司甚至不要求单元测试,有的公司要求单元测试,但对覆盖率
要求得不高;有的公司对覆盖率要求比较严,如:必须要用细粒度的单元测试,且核心代码单元测试的
覆盖率必须为100%,其余代码覆盖率不低于85%。
单元测试框架、工具:
单元测试的工具有JUnit、TestNG、Mockito、Unitils等,****其中JU********nit、****TestNG比较主流。
JUnit4相关注解:
- @BeforeClass:在所有测试方法前执行一次,一般在其中写上整体初始化的代码。
- @AfterClass:在所有测试方法后执行一次,一般在其中写上销毁和释放资源的代码。
- @Before:在每个测试方法前执行,一般用来初始化方法(比如我们在测试别的方法时,
类中与其他测试方法共享的值已经被改变,为了保证测试结果的有效性,我们会在@Before注解的方法中重置数据)。
- @After:在每个测试方法后执行,在方法执行完成后要做的事情 。
- @Test(timeout = 1000):指明要被测试的方法(测试方法执行超过1000毫秒后算超时,测试将失败)。
- @Test(expected = Exception.class):测试方法期望得到的异常类,如果方法执行没有抛出指定的异常,则测试失败。
- @Ignore:执行测试时将忽略掉此方法,如果用于修饰类,则忽略整个类。
- @Test:指明要被测试的方法。
- @RunWith:指定用什么方式策略去运行这些测试集、类、方法。
- @ActiveProfiles("xxx"):在测试的时候启用某些Profile的Bean。
注:JUnit5不仅新增了@ParameterizedTest注解等,还对JUnit4的部分注解进行了调整,
如:注解名称发生了变化(功能几乎没变)等。
注:这些注解的使用方式比较简单,下面的具体示例中,并不以示例上述注解为主。
断言Assert:
程序员在测试时,使用断言来判断某些逻辑条件必须满足。断言判断为真,下面的一些业务逻辑才可以进行下去,如果断言判断为假,程序就会"报错"甚至是"崩溃"。
断言与异常、错误的区别:
"断言"通常是给程序开发人员自己使用,并且在开发测试期间使用。而异常等在程序运行期间触发。通常"断言"触发后程序"崩溃"退出,不需要从错误中恢复。而"异常"通常会使用try/catch等结构从错误中恢复并继续运行程序。
断言简单使用示例:
断言Assert的方法很多:
注:
与断言(assert)相似的叫假设(assume)。当断言不成立时,当次测试的结果状态将会是测试失败;而当假设
不成立时,当次测试的结果状态不是失败,而是假设。断言用得想多一些,本人也不介绍假设。
注:
**未使用断言的单元测试,不是一个合格的单元测试;**使用System.out…的单元测试不是一个合格的单元测试;
需要人员盯着观察的单元测试,不是一个合格的单元测试。由于本文只是测试使用,未使用断言,啊~这不
是一个合格的单元测试。
JUnit使用示例:
声明:最基础的JUnit测试,本文不再给出,
下面给出的是模拟Web请求URL的单元测试。
软硬件环境:jdk1.8、win7、springboot1.5.2。
准备工作:在pom.xml引入JUnit依赖
Controller测试示例一:
先给出SpringBoot中编写的用于测试的controller类:
其余地方都和正常的SpringBoot编写一样,下面展示的是test文件夹下的test单元测试类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62 1import com.AsipreApplication;
2import org.junit.Assert;
3import org.junit.Before;
4import org.junit.Test;
5import org.junit.runner.RunWith;
6import org.springframework.beans.factory.annotation.Autowired;
7import org.springframework.boot.test.context.SpringBootTest;
8import org.springframework.http.MediaType;
9import org.springframework.test.context.junit4.SpringRunner;
10import org.springframework.test.context.web.WebAppConfiguration;
11import org.springframework.test.web.servlet.MockMvc;
12import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
13import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
14import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
15import org.springframework.test.web.servlet.setup.MockMvcBuilders;
16import org.springframework.web.context.WebApplicationContext;
17
18@RunWith(SpringRunner.class) // 指定测试启动器
19@SpringBootTest(classes = AsipreApplication.class) // 指定SpringBoot工程的启动类
20@WebAppConfiguration // Web项目中,Junit需要模拟ServletContext,由该注解提供
21public class DemoApplicationTests {
22
23 private MockMvc mockMvc;
24
25 @Autowired
26 private WebApplicationContext webApplicationContext;
27
28 @Before
29 public void setUp() {
30 this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
31 }
32
33 @Test
34 public void controllerJUnitTest() throws Exception {
35 String responseString = mockMvc
36 .perform(MockMvcRequestBuilders.get("/myJunitTest") // 请求的URL,请求的方法是get
37 .contentType(MediaType.APPLICATION_FORM_URLENCODED) // 数据的格式
38 .param("username", "zhangSan") // 添加参数
39 .param("myname", "JustryDeng"))
40 .andExpect(MockMvcResultMatchers.status().isOk()) // 返回的状态是200
41 .andDo(MockMvcResultHandlers.print()) // 打印出请求和相应的内容
42 .andReturn().getResponse().getContentAsString(); // 将响应的数据转换为字符串
43 System.out.println("返回的字符串数据 >>>>" + responseString);
44 }
45
46 public int getArraySum(int[] arr) {
47 int sum = 0;
48 for (int tem : arr) {
49 sum += arr[tem];
50 }
51 return sum;
52 }
53
54 @Test
55 public void singleJUnitTest() {
56 int[] arr = {1, 2, -4, 8, 4, -4, 6, -2, 1};
57 int sumValue = getArraySum(arr);
58 // 断言
59 Assert.assertEquals(12, sumValue);
60 }
61}
62
注:JUnit4时,RunWith一定要有,否则可能导致@Autowired注入的对象为null。
注:如果只是测试Controller类中方法的功能的话,也可以直接@Autowired获取到Controller类实例,然后进行打点
调用进行测试。
这里给出上例里controllerJUnitTest方法print出来的内容,并进行说明
:
由此可见,我们在测试Controller时,不仅可以设置请求的方式,还可以作出其他各种各样的设置。
Controller测试示例二:
**说明
:**可以不使用
@WebAppConfiguration ,而直接使用
webEnvironment =
SpringBootTest
.WebEnvironment.RANDOM_PORT来模拟Web环境
Controller中是这样的:
Test类是这样的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39 1import org.junit.Test;
2import org.junit.runner.RunWith;
3import org.springframework.beans.factory.annotation.Autowired;
4import org.springframework.boot.test.context.SpringBootTest;
5import org.springframework.boot.test.web.client.TestRestTemplate;
6import org.springframework.boot.web.server.LocalServerPort;
7import org.springframework.http.ResponseEntity;
8import org.springframework.test.context.junit4.SpringRunner;
9
10@RunWith(SpringRunner.class)//指定测试的运行环境
11//webEnvironment模拟提供web环境支持
12@SpringBootTest(classes = AbcSpringBootDemoApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
13public class AbcSpringBootDemoApplicationTests {
14 /**
15 * SpringBootTest.WebEnvironment.RANDOM_PORT其中RANDOM_PORT指自动随机获取一个可用的端口
16 * 通过@LocalServerPort注解将这个端口号赋值给port
17 */
18 @LocalServerPort
19 private int port;
20
21 /**
22 * RestTemplate对象在底层通过使用 java.net 包下的实现创建 HTTP 请求,
23 * 可以通过使用 ClientHttpRequestFactory 指定不同的HTTP请求方式
24 */
25 @Autowired
26 private TestRestTemplate restTemplate;
27
28 /**
29 * 向"/JUnitTest"地址发送请求,并打印返回结果
30 */
31 @Test
32 public void contextLoads() {
33 ResponseEntity<String> response = this.restTemplate.getForEntity(
34 "http://localhost:" + this.port + "/JUnitTest",
35 String.class,"");
36 System.out.println("响应的body信息为:" + response.getBody());
37 }
38}
39
提示:RestTemplate当然可以实现各种请求(get、post、put、delete等等),具体怎么进行,可参考本人的这篇博客。
注:如果只是测试Controller类中方法的功能的话,也可以直接@Autowired获取到Controller类实例,然后进行打
点调用进行测试。
进行JUnit测试(P.S.没使用断言的单元测试不是一个合格的单元测试),打印出:
单元测试时的事务回滚:
在单元测试时,有时我们会进行事务回滚来恢复现场,设置方式为:在类上或方法上加
@Transactional注解,如: