引言
JUnit作为Java语言的测试框架,在测试驱动开发(TDD)下扮演重要的角色。众所周知,无论开发大型项目还是一般的小型项目,
单元测试都至关重要。单元测试为软件可发测试维护提供了很大的便利。JUnit 4 作为最新版本,增添了许多新的特性,
结合Hamcrest,可以写出很多灵活的测试。从JUnit 4 开始 jar包放在org.junit包下。代码已经托管在GitHub上。
为了以后测试方便,自定义了一个JUnit的类库,把几个重要的jar包导在一个类库,
这样,以后的项目工程需要写单元测试,直接导入这个类库,不需要重新导入各种jar包。
自定义类库结构如下:
关于如何自定义类库,Build Path -> Configure Build Path... -> Add Library... -> User Library... -> Next ......,
常规的单元测试,是在工程下新建一个Source Folder和src目录下相同的包名结构,测试A类,就在相对应的包下
创建一个测试类ATest,测试fun方法,就相应的创建测试方法testFun()。本文单纯学习JUnit特性,没有采用这种方式。
ok,废话到此为止,开始测试。文章略长,按需阅读。
断言 - Assertions
在JUnit 之前版本,经常会使用assertTrue,assertEquals,assertNull等断言,根据字面意思就很容易知道函数的用途。
JUnit 4 结合Hamcrest退出更强大的断言AssertThat(),相比于之前的assertXXX,assertThat代码风格将变得统一,更容易维护;
配合Hamcrest的Matchers, 可以写出很多灵活的测试,下面会提到;另一个优点更倾向于英语语法,不像"谓宾主"
(如:assertEquals(9, x)) 语法模式拗口,assertThat使用类型"主谓宾"的语法模式(如:assertThat(x, is(9)),可能是受母语的影响,
这条优点感触不是很深刻; 下面要说的这个优点比较实用,AssertThat测试失败会提供一些可读的描述性错误信息,
而assertXXX不会(当然可以手动去写),assertThat会举例说明; Last but not the least(v~v高考残留的英语记忆),
可发人员可以实现Matcher接口,自定义匹配符,这个功能很强大。
注意:测试的时候需要引入一些类,有些类(Assert, Matchers)比较特殊,采用静态引入的方式,
好处在于可以直接使用这些类的静态方法(assertTure, assertThat),而不用使用类名.方法名的方式。
assertXXX
import static org.junit.Assert.*; import org.junit.Test; /** * AssertXXX测试 * @author michael */ public class AssertTests { @Test public void testAssertArrayEquals() { byte[] expected = "trial".getBytes(); byte[] actual = "trial".getBytes(); assertArrayEquals("failure - byte arrays not same", expected, actual); } @Test public void testEquals() { assertEquals("failure - strings are not equal", "text", "text"); } @Test public void testAssertTrue() { assertTrue("failure - should be true", true); } @Test public void testFalse() { assertFalse("failure - should be false", false); } @Test public void testAssertNotNull() { assertNotNull("should not be null", new Object()); } @Test public void testAssetNull() { assertNull("should be null", null); } @Test public void testAssertNotSame() { assertNotSame("should not be same Object", new Object(), new Object()); } @Test public void testAssertSame() { Integer aNumber = Integer.valueOf(985); assertSame("should be same", aNumber, aNumber); } }
assertThat
import static org.junit.Assert.*; import static org.hamcrest.Matchers.*; import java.util.Arrays; import org.junit.Test; public class AssertThatTests { //JUnit Matchers assertThat @Test public void testAssertThatBothContainsString() { assertThat("albumen", both(containsString("a")).and(containsString("b"))); } @Test public void testAssertThathasItemsContainsString() { assertThat(Arrays.asList("one", "two", "three"), hasItems("one", "three")); } @Test public void testAssertThatEveryItemContainsString() { assertThat(Arrays.asList(new String[] {"fun", "ban", "net"}), everyItem(containsString("n"))); } /** * Core Hamcrest Matchers with assertThat * 组合使用多种匹配符 */ @Test public void testAssertThatHamcrestCoreMatchers() { assertThat("good", allOf(equalTo("good"), startsWith("good"))); assertThat("good", not(allOf(equalTo("good"), equalTo("bad")))); assertThat("good", anyOf(equalTo("good"), equalTo("bad"))); assertThat(3, not(either(equalTo(6)).or(equalTo(9)))); assertThat(new Object(), not(sameInstance(new Object()))); } /** * Readable failure message * assertThat会提供可读性的错误信息,assertTrue不会 */ @Test public void testFailureMessage() { String s = "coour"; //assertTrue(s.contains("color") || s.contains("colour")); assertThat(s, anyOf(containsString("color"), containsString("colour"))); } }
套件测试 - Aggregating tests in Suites
如果你写了一系列的测试类(十几个甚至几十个),你不可能一个一个的去测试,此时,套件测试就会派上用场。
创建一个空类,不需要为这个类定义任何东西,只需要在这个类的头部添加注解@RunWith(Suite.class)。
@Suite.SuiteClasses。在SuiteClasses里添加需要测试的类。创建的这个类只是作为一个载体,承载上面的注解。
import org.junit.runner.RunWith; import org.junit.runners.Suite; import assertions.AssertTests; import assertions.AssertThatTests; @RunWith(Suite.class) @Suite.SuiteClasses({ //添加需要测试的类 AssertTests.class, AssertThatTests.class // more test classes }) public class FutureTestSuite { /** * the class remains empty, * used only a holder for the above annotations. */ }
What‘s the difference between failure and error in JUnit
顺序测试 - Test Execution Order
顺序测试的应用场景不是很多,当你想让你写的一系列测试方法按照一定的顺序执行才会用到。
添加@FixMethodOrder注解,有三种常用的运行顺序定义在枚举类MethodSorters内。
DEFAULT、JVM、NAME_ASCENDING。
DEFAULT:默认,比较方法名的hashCode值。
JVM:依赖JVM的实现,不同机器上可能有所不同。
NAME_ASCENDING:按照测试函数方法名升序。
import org.junit.FixMethodOrder; import org.junit.Test; import org.junit.runners.MethodSorters; @FixMethodOrder(MethodSorters.NAME_ASCENDING) //@FixMethodOrder(MethodSorters.DEFAULT) //@FixMethodOrder(MethodSorters.JVM) public class TestMethodOrder { @Test public void testA() { System.out.println("first"); } @Test public void testB() { System.out.println("second"); } @Test public void testC() { System.out.println("third"); } }
异常测试 - Exception Testing
开发者在编写程序时,有些代码可能会抛出异常。
如何测试这些代码是否按照我们预想的抛出异常,此时就需要用到异常测试。
@Test注解有一个可选择的参数expected,用于指定可能抛出的异常。例如:
Expected Exceptions
import java.util.ArrayList; import org.junit.Test; public class TestException { @SuppressWarnings("unused") private double result; @Test(expected = ArithmeticException.class) public void divide() { result = 1/0; } /** * The expected parameter should be used with care. * The test will pass if any code in the method throws IndexOutOfBoundsException */ @Test(expected = IndexOutOfBoundsException.class) public void empty() { new ArrayList<Object>().get(0); } }
不过,expected参数应当谨慎使用,因为,如果测试方法任意一处代码抛出expected指定的异常,
测试都会通过,无法准确定位哪处代码抛出的异常。长远考虑,推荐使用ExpectedException rule。
关于Rule会在另一篇文章中介绍。
ExceptedException rule
上面的测试方法对于测试简单的例子比较适用,但它存在一定的限制,比如开发者无法测试异常信息。
当然JUnit3.x 提供了Try/Catch Idiom可以预测异常和异常信息。JUnit4则提供了ExpectedException rule。
不同的是,JUnit4 expectedMessage除了可以预测可能抛出的异常信息,还可以与Hamcrest的Matchars配合 使用,
编写出更加灵活的测试。例如:
import static org.hamcrest.Matchers.containsString; import java.util.ArrayList; import java.util.List; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; public class TestExceptionRule { //Expected Exception Rule @Rule public ExpectedException thrown =ExpectedException.none(); /** * This rule lets you indicate not only what exception you are expecting, * but also the exception message you are expecting: */ @Test public void shouldTestExceptionMessage() { List<Object> list = new ArrayList<Object>(); thrown.expect(IndexOutOfBoundsException.class); thrown.expectMessage("Index: 0, Size: 0"); thrown.expectMessage(containsString("Size: 0")); list.get(0); } }
测试忽略 - Ignore tests
如果在某次测试中想要跳过某个测试方法,就使用@Ignore注解。
当然直接注释掉@Test注解同样可以跳过该测试方法,不同的是,
注释掉在测试结果中不会显示,而使用@Ignore会显示该方法被忽略。
import org.junit.Ignore; import org.junit.Test; public class TestIgnore { @Test public void testA() { System.out.println("executed"); } @Ignore @Test public void testB() { System.out.println("ignored"); } }
超时测试 - Timeout for tests
超时测试,顾名思义,测试方法超过指定的时间就会报Errors,注意不是Failures。
Why is JUnit timeout an Error Not Failure
开发人员可以为单独某个方法设置超时时间,也可以为整个测试类设置统一超时时间。
单独为一个方法设置超时时间,单位毫秒
import org.junit.Test; public class TestTimeout { @Test(timeout=100) public void testWithTimeOut() { for( ; ;) { } } }
为整个类设置超时时间,单位毫秒
import org.junit.Rule; import org.junit.Test; import org.junit.rules.Timeout; public class TestTimeoutRule { public static int n; @Rule public Timeout globalTimeout = new Timeout(100); @Test public void testInfiniteLoop1() { n++; for( ; ;) { } } @Test public void testInfiniteLoop2() { n++; for( ; ;) { } } }
结束语
关于Parameterized tests、Rules、Theories等特性将在另一篇文章介绍。
本文为笔者学习JUnit的学习笔记,不免有误解偏差,还望读者指点修正。
Keeps the bar green to keep the code clean.