JUnit单元测试
JUnit单元测试
目录
相关知识 :
- 单元测试是编写的一小段代码,用于检验目标代码的一个很小的、很明确的功能模块是否正确。
- 简单地讲,单元测试,就是对代码功能是否达到预期要求的测试。
- 单元测试:方便,效率高,不必启动繁重的服务等。
单元测试覆盖率 :
粗粒度覆盖率 :
粗粒度的覆盖包括类覆盖和方法覆盖两种 :
- 类覆盖是指类中只要有方法或变量被测试用例调用或执行到,那么久说这个类被测试覆盖了。
- 方法覆盖是指只要在测试用例执行过程中,某个方法被调用了,则无论执行了该方法中的多少行代码,都可以认为该方法被覆盖了。
细粒度覆盖率 :
行覆盖(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单元测试类:
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 注解 ,如: