JUnit单元测试

释放双眼,带上耳机,听听看~!

目录

相关知识

单元测试覆盖率

       粗粒度覆盖率

       细粒度覆盖率

单元测试框架、工具

        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等结构从错误中恢复并继续运行程序。

断言简单使用示例:

JUnit单元测试

断言Assert的方法很多:

JUnit单元测试

注:
与断言(assert)相似的叫假设(assume)。当断言不成立时,当次测试的结果状态将会是测试失败;而当假设
不成立时,当次测试的结果状态不是失败,而是假设。断言用得想多一些,本人也不介绍假设。

注:
**未使用断言的单元测试,不是一个合格的单元测试;**使用System.out…的单元测试不是一个合格的单元测试;
需要人员盯着观察的单元测试,不是一个合格的单元测试。由于本文只是测试使用,未使用断言,啊~这不
是一个合格的单元测试。


JUnit使用示例:

声明:最基础的JUnit测试,本文不再给出,
下面给出的是模拟Web请求URL的单元测试。

软硬件环境:jdk1.8、win7、springboot1.5.2。

准备工作:在pom.xml引入JUnit依赖

JUnit单元测试

 

Controller测试示例一:

先给出SpringBoot中编写的用于测试的controller类:

JUnit单元测试

其余地方都和正常的SpringBoot编写一样,下面展示的是test文件夹下的test单元测试类:

JUnit单元测试


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出来的内容,并进行说明

JUnit单元测试

 

由此可见,我们在测试Controller时,不仅可以设置请求的方式,还可以作出其他各种各样的设置。

Controller测试示例二:

**说明
:**可以不使用
@WebAppConfiguration ,而直接使用
webEnvironment =
SpringBootTest
.WebEnvironment.RANDOM_PORT来模拟Web环境

Controller中是这样的:

JUnit单元测试

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.没使用断言的单元测试不是一个合格的单元测试),打印出:

JUnit单元测试


单元测试时的事务回滚:

在单元测试时,有时我们会进行事务回滚来恢复现场,设置方式为:在类上或方法上加
@Transactional注解
,如:

JUnit单元测试


给TA打赏
共{{data.count}}人
人已打赏
安全技术

详解Node.js API系列 Crypto加密模块(2) Hmac

2021-12-21 16:36:11

安全技术

从零搭建自己的SpringBoot后台框架(二十三)

2022-1-12 12:36:11

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索