最近新项目对单元测试要求很高,对我来说是一种新的挑战。用了几天,发现单元测试真的能减少很多愚蠢的错误,也正是因为我之前从来没有用过,才感受特别明显。所以整理一下单元测试的一些内容。
1 单元测试
1.1 什么是单元测试
所谓单元测试是测试应用程序的功能是否能够按需要正常运行,并且确保是在开发人员的水平上,单元测试生成图片。单元测试是一个对单一实体(类或方法)的测试。单元测试是每个软件公司提高产品质量、满足客户需求的重要环节。
1.2 单元测试的方式
单元测试一般分为人工测试和自动测试,人工测试就是类似开发者写完接口以后自己通过请求模拟器真的请求一遍试试看这样,而自动测试是借助工具支持并且利用自动工具执行用例。
人工测试每次回归都需要花费大量时间,并且很容易出错,自动测试则不然,所以借助工具和自动执行的自动测试才更有优势。
1.3 什么是测试用例
单元测试用例是一部分代码,可以确保另一端代码(方法)按预期工作。
好的测试用例有一下几个特点:
- 已知输入和预期输出,即在测试执行前就已知。
- 已知输入需要测试的先决条件,预期输出需要测试后置条件。
单元测试对测试用例有以下几个要求:
- 每一项需求至少需要两个单元测试用例:一个正检验,一个负检验。
- 如果一个需求有子需求,每一个子需求必须至少有正检验和负检验两个测试用例。
2 Junit
2.1 什么是Junit
JUnit 是一个 Java 编程语言的单元测试框架。
JUnit 在测试驱动的开发方面有很重要的发展,是起源于 JUnit 的一个统称为 xUnit 的单元测试框架之一。
JUnit 促进了“先测试后编码”的理念,强调建立测试数据的一段代码,可以先测试,然后再应用。这个方法就好比“测试一点,编码一点,测试一点,编码一点……”,增加了程序员的产量和程序的稳定性,可以减少程序员的压力和花费在排错上的时间。
JUnit 是一个回归测试框架,被开发者用于实施对应用程序的单元测试,加快程序编制速度,同时提高编码的质量。
2.2 Junit的特点
- JUnit 是一个开放的资源框架,用于编写和运行测试。
- 提供注释来识别测试方法。
- 提供断言来测试预期结果。
- 提供测试运行来运行测试。
- JUnit 测试允许你编写代码更快,并能提高质量。
- JUnit 优雅简洁。没那么复杂,花费时间较少。
- JUnit 测试可以自动运行并且检查自身结果并提供即时反馈。所以也没有必要人工梳理测试结果的报告。
- JUnit 测试可以被组织为测试套件,包含测试用例,甚至其他的测试套件。
- JUnit 在一个条中显示进度。如果运行良好则是绿色;如果运行失败,则变成红色。
2.3 编写一个简单的单元测试
package com.mrh.java.junit;
import org.junit.Assert;
import org.junit.Test;
public class SimpleJunitTest {
@Test
public void checkUserName() {
Assert.assertTrue(true);
}
}
这是一个总是正确的测试用例,我们可以从这个简单的测试用例开始介绍一下单元测试的写法。
我们写一个测试用例的步骤如下:
- 创建一个测试类SimpleJunitTest
- 在建好的测试类中加入一个测试方法checkUserName()
- 给测试类加上注解@Test
- 用断言也就是Assert方法来判断测试成功或失败
- 最后运行测试代码(可以选中测试方法右键选中用junit run,也可以整个方法用Junit run)
最后执行结果为:
从这个用例中我们可以看到@Test注解是标识一个方法为测试方法的关键,而在junit里类似这样的注解还有很多,接下来我们先说一下junit的注解用法。
2.4 Junit的注解使用
2.4.1 重点注解类型
- @Test:这个注释说明依附在 JUnit 的 public void 方法可以作为一个测试案例。
- @Before:有些测试在运行前需要创造几个相似的对象。在 public void 方法加该注释是因为该方法需要在 test 方法前运行。
- @After:如果你将外部资源在 Before 方法中分配,那么你需要在测试运行后释放他们。在 public void 方法加该注释是因为该方法需要在 test 方法后运行。
- @BeforeClass:在 public void 方法加该注释是因为该方法需要在类中所有方法前运行。
- @AfterClass:它将会使方法在所有测试结束后执行。这个可以用来进行清理活动。
- @Ignore:这个注释是用来忽略有关不需要执行的测试的。
2.4.2 不同注解的执行顺序
除了说明测试用例的@Test注解,我们会有很多的@Before, @BeforeClass,@After, @AfterClass注解,这些注解都是在被@Test注释的测试方法前和后执行的,我们用一个小例子来展示被这些注解注释的方法的执行规则究竟是什么。
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
public class SimpleJunitTest {
@BeforeClass
public static void beforeClass() {
System.out.println("beforeClass");
}
@Before
public void before() {
System.out.println("before");
}
@After
public void after() {
System.out.println("after");
}
@AfterClass
public static void afterClass() {
System.out.print("afterClass");
}
@Test
public void checkUserName1() {
System.out.println("1");
Assert.assertTrue(true);
}
@Test
public void checkUserName2() {
System.out.println("2");
Assert.assertTrue(true);
}
}
这个代码可以打印出所有方法的执行顺序,我们来看一看用junit run这个测试类的结果是:
从运行结果可以看出
- beforeClass() 方法首先执行,并且只执行一次。
- afterClass() 方法最后执行,并且只执行一次。
- before() 方法针对每一个测试用例执行,但是是在执行测试用例之前。
- after() 方法针对每一个测试用例执行,但是是在执行测试用例之后。
- 在 before() 方法和 after() 方法之间,执行每一个测试用例。
这就是每个注解对应的方法的执行顺序。
2.4.3 @Ignore注解的使用
@Ignore注解的作用是让这个测试用例本次执行的时候被忽略。
还是上面的那个代码,我给其中一个测试方法注上@Ignore,看一下执行情况。
public class SimpleJunitTest {
@BeforeClass
public static void beforeClass() {
System.out.println("beforeClass");
}
@Before
public void before() {
System.out.println("before");
}
@After
public void after() {
System.out.println("after");
}
@AfterClass
public static void afterClass() {
System.out.print("afterClass");
}
@Test
public void checkUserName1() {
System.out.println("1");
Assert.assertTrue(true);
}
@Ignore
@Test
public void checkUserName2() {
System.out.println("2");
Assert.assertTrue(true);
}
}
从结果可以看出,注解@Ingnore的方法真的被ignore了。
2.5 时间测试的方法
@Test注解可以加上参数timeout,来控制单测的时间,超过时间数则算作超时。
@Test(timeout= 2000)
public void checkUserName2() {
System.out.println("2");
int i = 1;
while(true) {
i++;
}
}
我们在测试方法里,写一个死循环,然后在@test上加上timeout=2000的参数,运行结果为:
测试方法2报超时错误。
2.6 异常测试
有时候我们会故意传入一些奇怪的参数来测试异常场景,这个时候Junit也允许你在@Test注解上加上另一种参数来预期异常的发生。
@Test(expected = ArithmeticException.class)
public void checkUserName3() {
System.out.println("3");
throw new ArithmeticException();
}
我故意在方法里抛出了一个异常,但是由于我已经预期了这个异常的出现,所以测试方法3是通过的。
2.7 参数化测试
有时候程序员需要用多组参数测试同一个逻辑,这个时候Junit提供了参数化测试。
不过这个时候需要用到另一个注解@RunWith(Parameterized.class)
public class PrimeNumberChecker {
public Boolean validate(final Integer primeNumber) {
for (int i = 2; i < (primeNumber / 2); i++) {
if (primeNumber % i == 0) {
return false;
}
}
return true;
}
}
import java.util.Arrays;
import java.util.Collection;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import static org.junit.Assert.assertEquals;
@RunWith(Parameterized.class)
public class PrimeNumberCheckerTest {
private Integer inputNumber;
private Boolean expectedResult;
private PrimeNumberChecker primeNumberChecker;
@Before
public void initialize() {
primeNumberChecker = new PrimeNumberChecker();
}
// Each parameter should be placed as an argument here
// Every time runner triggers, it will pass the arguments
// from parameters we defined in primeNumbers() method
public PrimeNumberCheckerTest(Integer inputNumber,
Boolean expectedResult) {
this.inputNumber = inputNumber;
this.expectedResult = expectedResult;
}
@Parameters
public static Collection primeNumbers() {
return Arrays.asList(new Object[][] {
{ 2, true },
{ 6, false },
{ 19, true },
{ 22, false },
{ 23, true }
});
}
// This test will run 4 times since we have 5 parameters defined
@Test
public void testPrimeNumberChecker() {
System.out.println("Parameterized Number is : " + inputNumber);
assertEquals(expectedResult,
primeNumberChecker.validate(inputNumber));
}
}
3 总结
以上就是junit在java中如何帮助我们进行单元测试的一些整理。