6. 忽略测试
public class DemoIgnoreTest { @Test @Ignore("This test method is ignored.") public void test() { System.out.println("DemoIgnoreTest.test"); } @Test public void test2() { System.out.println("DemoIgnoreTest.test2"); } }
7. 超时测试
在测试方法上的 @Test
注解上添加 timeout
属性可以指定该测试方法的执行超时时间,如果测试方法执行的耗时超过了该时间,则会抛出 TestTimedOutException
异常。
public class DemoTimeoutTest { @Test(timeout = 100) public void test() throws InterruptedException { TimeUnit.SECONDS.sleep(3); System.out.println("DemoTimeoutTest.test"); } }
8. 参数测试
在测试的过程中,我们经常会给定一些测试数据,来跑单元测试方法,这在测试场景中非常有用。参数测试使用 Parameterized
运行器来实现,首先定义一个静态方法,并且使用 @Parameters
注解,静态方法的返回值是数组或者列表,然后对应数据在测试类上定义字段,使用构造方法来注入测试数据。
@RunWith(Parameterized.class) public class DemoParameterTest { private int input1; private int input2; public DemoParameterTest(int input1, int input2) { this.input1 = input1; this.input2 = input2; } @Parameters public static Integer[][] data() { return new Integer[][]{ {1, 11}, {2, 22}, {3, 33}, {4, 44}, {5, 55} }; } @Test public void test() { System.out.println("DemoParameterTest.test::" + input1 + "-" + input2); } }
除了使用构造方法外,还可以在字段上面使用 @Parameter
注解来注入测试数据,在注解的属性中指定数据列与字段的对应,注意字段的访问权限必须是 public
。
@RunWith(Parameterized.class) public class DemoParameterTest { @Parameter public int input1; @Parameter(1) public int input2; @Parameters public static Integer[][] data() { return new Integer[][]{ {1, 11}, {2, 22}, {3, 33}, {4, 44}, {5, 55} }; } @Test public void test() { System.out.println("DemoParameterTest.test::" + input1 + "-" + input2); } }
有多少组数据,测试方法就会执行多少次,为了区分每个测试数据执行的展示结果,可以在 @Parameters
注解上使用 name
属性来指定测试结果命名,命名的规则如下:
{index}
:当前参数的索引{0}, {1}, …
:第1个,第2个等等的参数值
@Parameters(name = "{index}:{0}-{1}" ) public static Integer[][] data() { return new Integer[][]{ {1, 11}, {2, 22}, {3, 33}, {4, 44}, {5, 55} }; }
例如上面示例在 IDEA 中执行的结果如下:
9. 理论测试
和参数测试类似,我们也可以使用理论测试来指定测试数据。理论测试使用 Theories
运行器来实现,可以在测试类中定义常量字段来指定测试数据,在字段上使用 @DataPoint
注解来标记,然后在测试方法定义和字段类型一致的参数就能注入数据。
@RunWith(Theories.class) public class DemoTheoriesTest { @DataPoint public static String GOOD_USERNAME = "CodeArtist"; @DataPoint public static String USERNAME_WITH_SLASH = "Hello CodeArtist"; @Theory public void test(String str) { System.out.println("DemoTheoriesTest.test::" + str); } }
也可以直接在测试方法的参数上使用 @TestedOn
来指定数据,如果有多个参数,会运行参数组合的每一种情况。
@RunWith(Theories.class) public class DemoTheoriesTest { @Theory public void test(@TestedOn(ints = {1, 2, 3}) int input1, @TestedOn(ints = {11, 22, 33}) int input2) { System.out.println("DemoTheoriesTest.test::" + input1 + "-" + input2); } }
除了前面的方法指定数据外,JUnit 也支持自定义注解来指定数据。自定义一个注解如下:
@Target(PARAMETER) @Retention(RetentionPolicy.RUNTIME) @ParametersSuppliedBy(BetweenSupplier.class) public @interface Between { int first(); int last(); }
实现 ParameterSupplier
接口编写自定义注解的规则实现如下:
public class BetweenSupplier extends ParameterSupplier { @Override public List<PotentialAssignment> getValueSources(ParameterSignature sig) throws Throwable { List<PotentialAssignment> list = new ArrayList<>(); Between between = sig.getAnnotation(Between.class); for (int i = between.first(); i <= between.last(); i++) { list.add(PotentialAssignment.forValue("between", i)); } return list; } }
使用自定义的注解来指定参数,也会运行所有参数组合的情况。
@RunWith(Theories.class) public class DemoTheoriesTest { @Theory public void test(@Between(first = 1, last = 3) int input1, @Between(first = 4, last = 6) int input2) { System.out.println("DemoTheoriesTest.test::" + input1 + "-" + input2); } }
10. 测试规则
规则可以为测试类中的每个测试方法灵活地添加扩展或重定义测试方法的行为,开发人员可以使用 JUnit 内置规则或自定义测试规则,方法级规则在规则字段上使用 @Rule
注解来指定,类级规则在规则字段上使用 @ClassRule
来指定。
10.1 内置规则
ExternalResource
ExternalResource
规则类是一个抽象类,可以为测试环境提供外部资源的访问,比如在测试前建立一个访问(文件、套接字、服务、数据库连接等等),在测试后关闭一个访问。
public class DemoExternalResourceTest { @Rule public final ExternalResource externalResource = new ExternalResource() { @Override protected void before() { System.out.println("DemoExternalResourceTest.before"); } @Override protected void after() { System.out.println("DemoExternalResourceTest.after"); } }; @Test public void test() { System.out.println("DemoExternalResourceTest.test"); } }
TemporaryFolder
TemporaryFolder
规则继承了 ExternalResource
抽象类,实现了在测试过程中创建临时文件或文件夹的功能,在测试过程中创建的文件都会在测试后自动删除。
public class DemoTemporaryFolderTest { @Rule public final TemporaryFolder folder = new TemporaryFolder(); @Test public void test() throws IOException { File createdFile = folder.newFile("codeartist.txt"); File createdFolder = folder.newFolder("CodeArtist"); System.out.println("DemoTemporaryFolderTest.test#createdFile: " + createdFile); System.out.println("DemoTemporaryFolderTest.test#createdFolder: " + createdFolder); } }
Verifier
Verifier
规则类是一个抽象类,它可以在测试方法运行完后执行一些逻辑,所以可以用来校验测试的结果。
public class DemoVerifierTest { private static String result = ""; @Rule public final Verifier verifier = new Verifier() { @Override protected void verify() { result += "verify"; } }; @Test public void test() { System.out.println("DemoVerifierTest.test::" + result); } @Test public void test2() { System.out.println("DemoVerifierTest.test::" + result); } }
ErrorCollector
ErrorCollector
规则继承了 Verifier
抽象类,它是一个错误收集器,在测试方法运行时,遇到第一个异常会继续执行,先收集所有的异常然后一次性报告异常信息。
public class DemoErrorCollectorTest { @Rule public final ErrorCollector collector = new ErrorCollector(); @Test public void test() { try { System.out.println("DemoErrorCollectorTest.test::First exception"); int a = 1 / 0; } catch (Exception e) { collector.addError(e); } try { System.out.println("DemoErrorCollectorTest.test::Second exception"); String a = null; System.out.println(a.getBytes()); } catch (Exception e) { collector.addError(e); } } }
TestWatcher
TestWatcher
规则类是一个抽象类,它可以监视一个测试方法执行的生命周期动作,但不能修改,例如可以在测试开始时、成功执行时、执行结束时添加逻辑。
public class DemoTestWatcherTest { @Rule public final TestWatcher watcher = new TestWatcher() { @Override protected void starting(Description description) { System.out.println("DemoTestWatcherTest.starting"); } @Override protected void succeeded(Description description) { System.out.println("DemoTestWatcherTest.succeeded"); } @Override protected void finished(Description description) { System.out.println("DemoTestWatcherTest.finished"); } }; @Test public void test() { System.out.println("DemoTestWatcherTest.test"); } }
TestName
TestName
规则可以获取当前运行的测试方法的名称。
public class DemoTestNameTest { @Rule public final TestName testName = new TestName(); @Test public void test() { System.out.println("DemoTestNameTest.test::" + testName.getMethodName()); } }
Timeout
Timeout
规则可以指定测试类中所有测试方法的超时时间。
public class DemoTimeoutTest { @Rule public final Timeout timeout = Timeout.seconds(1); @Test public void test() throws InterruptedException { TimeUnit.SECONDS.sleep(2); System.out.println("DemoTimeoutTest.test"); } }
ExpectedException
ExpectedException
规则可以指定测试方法抛出预期的异常或存在预期的异常消息描述,如果没有达到预期会抛出 AssertionError
。
public class DemoExpectedExceptionTest { @Rule public final ExpectedException exception = ExpectedException.none(); @Test public void test() { exception.expect(NullPointerException.class); exception.expectMessage("happened?"); exception.expectMessage(startsWith("What")); throw new NullPointerException("What happened?"); } }
ClassRule
@ClassRule
注解扩展了 @Rule
的方法级规则,在定义的规则字段上需要添加static
修饰,类级别规则在测试类运行时执行,与测试方法无关。
public class DemoClassRuleTest { @ClassRule public static final ExternalResource r = new ExternalResource() { @Override protected void before() { System.out.println("DemoClassRuleTest.before"); } @Override protected void after() { System.out.println("DemoClassRuleTest.after"); } }; @Test public void test() { System.out.println("DemoClassRuleTest.test"); } @Test public void test2() { System.out.println("DemoClassRuleTest.test2"); } }
RuleChain
RuleChain
规则可以定义规则链,也就是说可以在当前测试类中定义多个规则。
public class DemoRuleChainTest { @Rule public final RuleChain chain = RuleChain .outerRule(new TestLogger("outer rule")) .around(new TestLogger("around1 rule")) .around(new TestLogger("around2 rule")); @Test public void test() { System.out.println("DemoRuleChainTest.test"); } }
10.2 自定义规则
我们通过实现 TestRule
接口来自定义规则,也可以继承内置的基础规则抽象类来实现。
public class TestLogger implements TestRule { private final String message; public TestLogger(String message) { this.message = message; } @Override public Statement apply(Statement base, Description description) { return new Statement() { @Override public void evaluate() throws Throwable { System.out.println("TestLogger.evaluate::" + message); base.evaluate(); } }; } }
11. 测试卡具
测试卡具可以在测试方法执行的前面或者后面执行一些逻辑,@Before
注解修饰的方法在所有测试方法执行前都会执行,相反 @After
注解修饰的方法在所有测试方法执行后都会执行,它们是方法级别的,而 @BeforeClass
和 @AfterClass
注解修饰的方法是类级别的,方法必须添加 static
修饰,而且不管测试类中存在多个测试方法,它们在测试方法执行前或执行后只执行一次。
public class DemoFixtureTest { @BeforeClass public static void beforeClass() { System.out.println("DemoFixtureTest.beforeClass"); } @AfterClass public static void afterClass() { System.out.println("DemoFixtureTest.afterClass"); } @Before public void before() { System.out.println("DemoFixtureTest.before"); } @After public void after() { System.out.println("DemoFixtureTest.after"); } @Test public void test() { System.out.println("DemoFixtureTest.test"); } @Test public void test2() { System.out.println("DemoFixtureTest.test2"); } }
测试卡具可以实现下面功能:
- 在测试前准备输入数据,或配置 Mock 对象
- 加载一个特定的数据库来准备数据
- 为某些测试类创建一些初始化状态