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等,其中JUnit、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单元测试类:

import com.AsipreApplication;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

@RunWith(SpringRunner.class) // 指定测试启动器
@SpringBootTest(classes = AsipreApplication.class) // 指定SpringBoot工程的启动类
@WebAppConfiguration // Web项目中,Junit需要模拟ServletContext,由该注解提供
public class DemoApplicationTests {
    
    private MockMvc mockMvc;

    @Autowired
    private WebApplicationContext webApplicationContext;

    @Before
    public void setUp() {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    }

    @Test
    public void controllerJUnitTest() throws Exception {
        String responseString = mockMvc
                .perform(MockMvcRequestBuilders.get("/myJunitTest") // 请求的URL,请求的方法是get
                        .contentType(MediaType.APPLICATION_FORM_URLENCODED) // 数据的格式
                        .param("username", "zhangSan") // 添加参数
                        .param("myname", "JustryDeng"))
                .andExpect(MockMvcResultMatchers.status().isOk()) // 返回的状态是200
                .andDo(MockMvcResultHandlers.print()) // 打印出请求和相应的内容
                .andReturn().getResponse().getContentAsString(); // 将响应的数据转换为字符串
        System.out.println("返回的字符串数据 >>>>" + responseString);
    }

    public int getArraySum(int[] arr) {
        int sum = 0;
        for (int tem : arr) {
            sum += arr[tem];
        }
        return sum;
    }

    @Test
    public void singleJUnitTest() {
        int[] arr = {1, 2, -4, 8, 4, -4, 6, -2, 1};
        int sumValue = getArraySum(arr);
        // 断言
        Assert.assertEquals(12, sumValue);
    }
}

注:JUnit4时,RunWith一定要有,否则可能导致@Autowired注入的对象为null。

注:如果只是测试Controller类中方法的功能的话,也可以直接@Autowired获取到Controller类实例,然后进行打点
       调用进行测试。

这里给出上例里controllerJUnitTest方法print出来的内容,并进行说明

 

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

Controller测试示例二

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

Controller中是这样的:

Test类是这样的:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)//指定测试的运行环境
//webEnvironment模拟提供web环境支持
@SpringBootTest(classes = AbcSpringBootDemoApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class AbcSpringBootDemoApplicationTests {
	/**
	 * SpringBootTest.WebEnvironment.RANDOM_PORT其中RANDOM_PORT指自动随机获取一个可用的端口
	 * 通过@LocalServerPort注解将这个端口号赋值给port
	 */
	@LocalServerPort
	private int port;

	/**
	 * RestTemplate对象在底层通过使用 java.net 包下的实现创建 HTTP 请求,
	 * 可以通过使用 ClientHttpRequestFactory 指定不同的HTTP请求方式
	 */
	@Autowired
	private TestRestTemplate restTemplate;

	/**
	 * 向"/JUnitTest"地址发送请求,并打印返回结果
	 */
	@Test
	public void contextLoads() {
		ResponseEntity<String> response = this.restTemplate.getForEntity(
				"http://localhost:" + this.port + "/JUnitTest",
				          String.class,"");
		System.out.println("响应的body信息为:" + response.getBody());
	}
}

提示:RestTemplate当然可以实现各种请求(get、post、put、delete等等),具体怎么进行,可参考本人的这篇博客

注:如果只是测试Controller类中方法的功能的话,也可以直接@Autowired获取到Controller类实例,然后进行打
       点调用进行测试。

进行JUnit测试(P.S.没使用断言的单元测试不是一个合格的单元测试),打印出:


单元测试时的事务回滚

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


^_^ 若有不足,欢迎指正

^_^ 参考链接 (部分内容直接摘录自此链接)
              
https://blog.csdn.net/yangwei4321/article/details/76625696等

^_^ 参考书籍(部分内容直接摘录自此书籍,强烈推荐阅读)
             《码出高效Java开发手册》 杨冠宝(孤尽)、高海慧(鸣莎) 著

^_^ 本文已经被收录进《程序员成长笔记(二)》,笔者JustryDeng

  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值