JUNIT学习笔记
基本使用与知识储备
使用 Junit 只需要在 pom.xml 文件中的 dependencies 节点添加依赖:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
以用促学,来看下面的问题:
给定两个整数,被除数 dividend 和除数 divisor。将两数相除,要求不使用乘法、除法和 mod 运算符。返回被除数 dividend 除以除数 divisor 得到的商。
我的代码如下:
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class mytest {
public static int divide(int dividend, int divisor) {
int result = 0;
if (divisor == 0) {
throw new RuntimeException();
}
float remainder = dividend;
while (remainder >= divisor) {
result++;
remainder = remainder - divisor;
}
return result;
}
@Test
public void should_get_error_when_divisor_is_zero() {
assertEquals(3, divide(9, 0));
}
被除数为0的执行结果
测试超时
如果一个测试运行时间很长,往往意味着测试失败。junit 提供了一种方式结束它,使用 @Test 注解的 timeout 参数可以传入一个毫秒数。
如果这个测试的运行时间,超过了 timeout 允许的时间,junit 会中断测试线程,标记测试失败,并丢出异常。需要注意的是,junit 启动了另外一个线程并发出中断信号,如果测试代码无法被中断。
@Test(timeout = 1000)
public void test_with_timeout(){
…
}
其它注释
Junit 4 中有四个执行过程注解:
@BeforeClass 用于类加载首次执行,必须使用在静态方法上
@Before 用于实例方法,在每个测试用例之前执行
@After 用于实例方法,在每个测试用例之间执行
@AfterClass 用于类完成执行,必须使用在静态方法上
在 @Before 和 @After 方法之间,执行每一个测试用例。他们的执行顺序是:
@BeforeClass -> @Before -> @After -> @Test -> @AfterClass
如果因为某些原因(可能是快速修复 CI,避免影响队友工作),你需要快速忽略测试。尽量不要使用大面积注释代码的方法,请使用 @Ignore 注解,并加上原因,并随后修复。
单元测试用例设计
问题:回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数,回文字符串很好判断,但是回文数是实现呢?
等价划分和边界分析
• 最小值到 0 是两个边界
• 0 到 10 是一个边界(因为个位数一定满足回文数)
• 10 到最大值是一个边界
我们考虑6个用例,(这里也可以考虑7个,把-1考虑上)并把这些用例输入
下面是我的代码
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import static org.junit.Assert.*;
import java.util.Arrays;
import java.util.Collection;
@RunWith(Parameterized.class)
public class ParameterizedPractiseTest {
@Parameterized.Parameters
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][]{
{Integer.MIN_VALUE, false},
{0, true},
{5, true},
{10, false},
{11, true},
{Integer.MAX_VALUE, false}
});
}
private int input;
private boolean expected;
public ParameterizedPractiseTest(int input, boolean expected) {
this.input = input;
this.expected = expected;
}
public static boolean isPalindrome(int inputValue) {//检验回文的方法
if (inputValue < 0) {
return false;
}
int reverseValue = 0;
int intermediateValue = inputValue;
while (intermediateValue != 0) {
reverseValue = reverseValue * 10 + intermediateValue % 10;
intermediateValue /= 10;
}
return reverseValue == inputValue;
}
@Test
public void test() {
assertEquals(expected, isPalindrome(input));
}
}
运行结果
@RunWith(Parameterized.class)声明在类外,@Parameterized.Parameters,声明在一个静态方法的前面,其中的二级数组应当满足构造方法的格式
public ParameterizedPractiseTest(int input, boolean expected) {
this.input = input;
this.expected = expected;
}
@Parameterized.Parameters 定义了 6 条数据作为输入输出。这个注解允许传入一个模板,给6个独立的测试输出一个名称,便于识别。
@Parameterized.Parameters(name = “{index}input({0})should_be{1}”)
Runner 是 junit 比较高级的技术。另外一个第三方 JUnitParamsRunner,可以运行直接将 Parameters 注解组合应用于测试方法上,显得更为简洁。
JUnitParamsRunner 需要自行通过 maven 导入。
@RunWith(JUnitParamsRunner.class)
public class PersonTest {
@Test
@Parameters({"17, false",
"22, true" })
public void person_is_adult(int age, boolean valid) throws Exception {
assertThat(new Person(age).isAdult(), is(valid));
}
}
测试覆盖率
单元测试约定
- 测试用例的方法名称使用下划线,并表达出一个完整的句子,单元测试可以作为”活“文档。
- 测试文件的创建和业务代码类一一对应,并放置到和业务代码同级的测试模块中,例如 ”src/test/java“。IDE 工具可以自动识别,并通过快捷键跳转。
- 测试文件和业务代码文件同名,并使用 Test 结束。
- 按照 given/when/then 的风格组织测试代码
- 测试用例必须有充分的断言语句
- 单元测试不需要特别的设置就能运行
- 不允许注释单元测试方法,如果需要快速跳过,使用 @Ignore 注解
- 如果对全局对象造成改变,使用 @After 注解的方法清理,保持独立原则
- 如果被测试的类实现了接口,尽量通过接口的类型测试
- 配置合适的测试覆盖率,核心代码测试率为 100%。为不同的代码配置不同等级的测试覆盖率。
- 通用的准备工作,使用 @Setup 准备
- 通用的数据准备工作,可以通过抽出测试助手类完成
- 提交代码前保证单元测试通过
- 修改业务代码,同步修改单元测试,并补充足够的用例
Reference:本此学习过程受启发于Junit 单元测试基础 https://www.zhihu.com/tardis/sogou/art/156733597(zhihu.com),实现了文中的部分测试用例。
另外本文整理至博客留存。