JUnit是一个编写测试代码的框架,它是单元测试框架的xUnit体系结构中的一个,目前主要使用的是JUnit 4
和 JUnit 5
测试代码验证相应的代码是否产生了预期的状态/结果(状态测试)或按照预期的顺序执行事件(行为测试)
单元测试中测试的单元是一个方法,类等,其外部依赖会被移除
单元测试不适合测试复杂的用户界面或组件交互,这种情况应该使用集成测试(TestNG
)。
测试代码单独放在一个文件夹中,Maven和Gradle构建工具默认路径是src/test/java
依赖
添加JUnit 4依赖
Gradle:
dependencies {
testCompile 'junit:junit:4.12'
}
Maven:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
定义测试方法
一个JUnit测试是写在专门用于测试的类内部的方法,这个类叫做Test
类,通常以Test
作为类名称的结尾
JUnit使用注释将方法标记为测试方法并对其进行配置,测试方法使用@Test
注解标明
下表是了JUnit中4.x和5.x版本中基本的注释,这些注解都作用于方法
名称 | 描述 |
---|---|
import org.junit.* |
注解都位于该包下 |
@Test(expected = xxx.class , timeout = xx) |
表明该方法是测试方法,如果不抛出异常,就表示测试成功过,可以添加参数,expected 指定异常类型,如果不抛出异常或者抛出其他异常都算作是失败,只有抛出指定类型的异常才算成功,如果执行时间超过timeout ,算作失败 |
@Before |
用于public void 方法,在类中的每个测试之前执行,用于输入数据,初始化类等 |
@After |
用于public void 方法,该方法在执行每个测试后执行,用于释放资源等 |
@BeforeClass |
用于 public static void 的无参方法,在类中的任何测试方法之前运行一次 ,用于test之间共享的数据 |
@AfterClass |
用于 public static void 的无参方法,在类中的任何测试方法之前运行一次 ,用于释放BeforClass 申请的资源 |
@Ignore("Why disabled") |
用于禁止测试方法执行,例如测试代码需要修改,执行代价比较大 |
断言
测试方法内部经常使用assert,JUnit为每种基本数据类型,Object和数组都提供了断言方法,方法位于org.junit.Assert
类,参数顺序是期望值,之后是实际值,可以把String类型作为第一个参数,当期望值和实际值不一致时,抛出AssertionException
异常,String类型参数作为失败信息输出
方法示例:
assertArrayEquals(boolean[] expecteds, boolean[] actuals)//各种数据类型都有
assertArrayEquals(String message, boolean[] expecteds, boolean[] actuals)
assertTrue(boolean condition)
assertNotNull(String message, Object object)
assertThat(String reason, T actual, Matcher<? super T> matcher)
import org.junit.Test;
public class AssertTests {
@Test
public void testAssertArrayEquals() {
byte[] expected = "trial".getBytes();
byte[] actual = "trial".getBytes();
org.junit.Assert.assertArrayEquals("failure - byte arrays not same", expected, actual);
}
@Test
public void testAssertTrue() {
org.junit.Assert.assertTrue("failure - should be true", true);
}
}
JUnit测试套件
如果有多个测试类,可以创建一个测试套件把它们结合到一起,然后会按照顺序运行套件内的所有的测试类
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
@RunWith(Suite.class) //表明使用 org.junit.runners.Suite 运行测试类
@SuiteClasses({ // 告诉Suite runner运行的的测试类及顺序
MyClassTest.class,
MySecondClassTest.class })
public class AllTests {
// 这个类用作占位符,不需要具体的方法
}
禁用测试
一种是使用@Ignore
注解,另外可以在测试方法内部调用org.junit.Assume
类的方法,如果假定失败,中断测试,不再执行
// 在Linux系统下不再进行测试
Assume.assumeFalse(System.getProperty("os.name").contains("Linux"));
参数化测试
参数化测试类特点
- 只含有一个Test函数,这个函数会使用不同的参数多次执行
- 类名称通过
@RunWith(Parameterized.class)
注解标注 - 一个静态方法使用
@Parameters
注解,生成并返回测试数据, - 使用构造函数,接收输入数据,或者直接使用
@Parameter
注解public
字段,将输入数据直注入到字段中
import static org.junit.Assert.assertEquals;
import java.util.Arrays;
import java.util.Collection;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class FibonacciTest {
// 创建测试数据
@Parameters(name = "{index}: fib({0})={1}") // 可以通过测试数据生成测试名称,index 索引,{0}第一个参数值
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][] {
{ 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 }
});
}
//使用注解注入参数,Parameter(value)中的value对应于Parameters产生的输入参数数组的索引
@Parameter // 第一个参数,默认索引为0,省略
public /* NOT private */ int fInput;
@Parameter(1)
public /* NOT private */ int fExpected;
// 使用构造函数,处理输入输入
// private int input;
// private int expected;
// public FibonacciTest(int input, int expected) {
// this.input = input;
// this.expected = expected;
// }
@Test
public void test() {
assertEquals(fExpected, Fibonacci.compute(fInput));
}
}
public class Fibonacci {
...
}
JUnit 规则
@Rule
注解用来标出测试类的公共字段,字段类型为TestRule
用来控制一个或一组测试方法如何运行并报告,它可以实现之前通过方法注解(org.junit.Before
,org.junit.After
等)完成的所有功能,并且更加有效
多个TestRule可以应用到一个测试方法,TestRule接口有很多具体的实现
ErrorCollector
:在一个测试方法中收集多个异常
ExpectedException
:指定要抛出特定异常
ExternalResource
:抽象类,在测试之前启动外部资源,结束后关闭,例如启动和停止服务器,它是TemporaryFolder
的父类
TemporaryFolder
:创建临时文件,并在测试后删除
TestName
:用于在测试方法内部获得当前测试方法的名称
TestWatchman
:记录测试操作
Timeout
:设定时限,测试超时后失败
Verifier
:如果对象状态不正确,则测试失败
RuleChain
:用来设定TestRule
的执行顺序
public class RuleExceptionTesterExample {
@Rule
public ExpectedException exception = ExpectedException.none();
@Test
public void throwsIllegalArgumentExceptionIfIconIsNull() {
exception.expect(IllegalArgumentException.class);
exception.expectMessage("Negative value not allowed");
ClassToBeTested t = new ClassToBeTested();
t.methodToBeTest(-1);
}
}
自定义规则
大多数规则都可以通过扩展ExternalResource
实现,如果需要更多的测试信息,可以自己实现TestRule
接口
该例子中,TestLogger
为每个test提供一个命名logger
package org.example.junit;
import java.util.logging.Logger;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
public class TestLogger implements TestRule {
private Logger logger;
public Logger getLogger() {
return this.logger;
}
@Override
public Statement apply(final Statement base, final Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
logger = Logger.getLogger(description.getTestClass().getName() + '.' + description.getDisplayName());
base.evaluate();
}
};
}
}
使用该TestRule
import java.util.logging.Logger;
import org.example.junit.TestLogger;
import org.junit.Rule;
import org.junit.Test;
public class MyLoggerTest {
@Rule
public final TestLogger logger = new TestLogger();
@Test
public void checkOutMyLogger() {
final Logger log = logger.getLogger();
log.warn("Your test is showing!");
}
}
类别
对于一组测试类,通过@Category
标记类和方法所属的类别,Categories
runner会获取类别信息,并且只运行在@IncludeCategory
中指定的类别及其子类别
从给定的一组测试类中,只运行使用@IncludeCategory注释给出的类别或该类别的子类型注释的类和方法。
请注意,现在,使用@Category注释套件不起作用。 类别必须使用直接方法或类进行注释。
public interface FastTests {} // 类别标记
public interface SlowTests {}
public interface SmokeTests{}
public static class A {
@Test
public void a() {
fail();
}
@Category(SlowTests.class)
@Test
public void b() {
}
@Category({FastTests.class, SmokeTests.class})
@Test
public void c() {
}
}
@Category({SlowTests.class, FastTests.class})
public static class B {
@Test
public void d() {
}
}
@RunWith(Categories.class)
@IncludeCategory(SlowTests.class)
@SuiteClasses({A.class, B.class}) // Categories 是一种套件Suite
public static class SlowTestSuite {
// 运行 A.b 和 B.d, 不运行 A.a 和 A.c
}
@RunWith(Categories.class)
@IncludeCategory({FastTests.class, SmokeTests.class})
@SuiteClasses({A.class, B.class})
public static class FastOrSmokeTestSuite {
// 运行 A.c 和 B.d
}
可以在Gradle内指定要运行的JUnit类别
test {
useJUnit {
includeCategories 'org.gradle.junit.CategoryA'
excludeCategories 'org.gradle.junit.CategoryB'
}
}
运行测试代码
如果在IDE(NetBeans/Eclipse/IntelliJ IDEA等)内运行JUnit测试代码,直接以内置的图形化形式运行
也可以通过应用程序调用测试类,获取测试结果,核心是org.junit.runner.JUnitCore.runClasses(TestClass1.class, ...);
方法
import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;
public class MyTestRunner {
public static void main(String[] args) {
Result result = JUnitCore.runClasses(MyClassTest.class);
for (Failure failure : result.getFailures()) {
System.out.println(failure.toString());
}
}
}
也可以直接以命令形式运行
java org.junit.runner.JUnitCore TestClass1 [...other test classes...]
测试执行顺序
JUnit没有指定测试方法调用的顺序,编写测试代码时不应该假定任何顺序,即所有测试方法都可以以任意顺序执行,一个测试不应该依赖于其他测试。4.11 以后可以在测试类上添加注解来定义测试方法的执行顺序
MethodSorters.DEFAULT // 默认情况,执行顺序确定,但不确定具体如何
MethodSorters.JVM // 依照JVM返回的顺序,每次顺序都可能会变化
MethodSorters.NAME_ASCENDING // 通过测试方法的名称排序
// 示例
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class MethodOrderTest {
//测试方法
}
JUnit 5
JUnit 5 由几大不同的模块组成,这些模块分别来自三个不同的子项目
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
具体使用可以参考junit5 中文
Junit 4相关可以参考 junit4 wiki
网友评论