Junit4
断言
Junit提供了重载的原生类型、对象、数组(原生类型或对象)断言方法。参数的顺序是期待值和实际值。或者第一个参数可以是当测试失败的时候输出一个String类型的错误消息。有一个稍微不同的断言assertThat,assertThat参数为一个可选的错误消息,实际值和一个Matcher对象。注意 assertThat期待值与实际值的顺序与其他断言方法相反。
每个断言方法表示如下:
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.anyOf;
import static org.hamcrest.CoreMatchers.both;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.everyItem;
import static org.hamcrest.CoreMatchers.hasItems;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.util.Arrays;
import org.hamcrest.core.CombinableMatcher;
import org.junit.Test;
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 testAssertEquals() {
assertEquals("failure - strings are not equal", "text", "text");
}
@Test
public void testAssertFalse() {
assertFalse("failure - should be false", false);
}
@Test
public void testAssertNotNull() {
assertNotNull("should not be null", new Object());
}
@Test
public void testAssertNotSame() {
assertNotSame("should not be same Object", new Object(), new Object());
}
@Test
public void testAssertNull() {
assertNull("should be null", null);
}
@Test
public void testAssertSame() {
Integer aNumber = Integer.valueOf(768);
assertSame("should be same", aNumber, aNumber);
}
// JUnit Matchers assertThat
@Test
public void testAssertThatBothContainsString() {
assertThat("albumen", both(containsString("a")).and(containsString("b")));
}
@Test
public void testAssertThatHasItems() {
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("bad"), equalTo("good"))));
assertThat("good", anyOf(equalTo("bad"), equalTo("good")));
assertThat(7, not(CombinableMatcher.<Integer> either(equalTo(3)).or(equalTo(4))));
assertThat(new Object(), not(sameInstance(new Object())));
}
@Test
public void testAssertTrue() {
assertTrue("failure - should be true", true);
}
}
测试 fixtures
有四个fixture注解:两个用于类级别的fixture,另外两个用于方法级别的fixture。 在类级别,有@BeforeClass和@AfterClass,在方法(或测试)级别,有@Before和@After。
package test;
import java.io.Closeable;
import java.io.IOException;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
public class TestFixturesExample {
static class ExpensiveManagedResource implements Closeable {
@Override
public void close() throws IOException {}
}
static class ManagedResource implements Closeable {
@Override
public void close() throws IOException {}
}
@BeforeClass
public static void setUpClass() {
System.out.println("@BeforeClass setUpClass");
myExpensiveManagedResource = new ExpensiveManagedResource();
}
@AfterClass
public static void tearDownClass() throws IOException {
System.out.println("@AfterClass tearDownClass");
myExpensiveManagedResource.close();
myExpensiveManagedResource = null;
}
private ManagedResource myManagedResource;
private static ExpensiveManagedResource myExpensiveManagedResource;
private void println(String string) {
System.out.println(string);
}
@Before
public void setUp() {
this.println("@Before setUp");
this.myManagedResource = new ManagedResource();
}
@After
public void tearDown() throws IOException {
this.println("@After tearDown");
this.myManagedResource.close();
this.myManagedResource = null;
}
@Test
public void test1() {
this.println("@Test test1()");
}
@Test
public void test2() {
this.println("@Test test2()");
}
}
输出如下:
@BeforeClass setUpClass
@Before setUp
@Test test2()
@After tearDown
@Before setUp
@Test test1()
@After tearDown
@AfterClass tearDownClass
测试执行顺序
按照设计,Junit没有指定测试方法调用的执行顺序。到目前为止,方法只是按照反射API返回的顺序进行调用。但是,使用JVM命令是不明智的,因为Java平台没有指定任何特定的顺序,实际上JDK 7返回或多或少的随机顺序。当然,精心编写的测试代码不会做任何顺序的假设,但有些则需要,在某个特定的平台上一个可预测的测试失败要好与随机的测试失败。
从4.11版开始,JUnit将默认使用确定性但不可预测的顺序(MethodSorters.DEFAULT)。要更改测试执行顺序,只需使用@FixMethodOrder对测试类进行注明,并指定一个可用的MethodSorters:
@FixMethodOrder(MethodSorters.JVM):按照JVM返回的顺序执行测试方法。这个顺序每次运行可能都不一样。
@FixMethodOrder(MethodSorters.NAME_ASCENDING):按照字典顺序对测试方法名排序。
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
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");
}
}
以上代码会按照测试方法名的升序顺序执行
Suite聚合测试
使用Suite做为Runner允许我们手动创建一个包含其他类的suite。同JUnit 3.8.x 的static Test suite()方法一样。使用Suite,需要在类上使用注解@RunWith(Suite.class) 和 @SuiteClasses(TestClass1.class, …)。当你运行这个类的时候,它会运行suite中所有类的测试。
下面的类注明了suite注解,并且不需要其他实现。注意@RunWith注解,它指定使用JUnit 4测试Runner,org.junit.runners.Suite来运行这个特定的测试类。他需要与@Suite注解一起使用,@Suite告诉Suite Runner包含的测试类和顺序。
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
@RunWith(Suite.class)
@Suite.SuiteClasses({
TestFeatureLogin.class,
TestFeatureLogout.class,
TestFeatureNavigate.class,
TestFeatureUpdate.class
})
public class FeatureTestSuite {
// the class remains empty,
// used only as a holder for the above annotations
}
忽略测试
如果由于某些原因,你不希望测试失败,你只想忽略它,你可以暂时禁用一个测试。
要忽略JUnit中的测试,你可以将方法注释掉,或者删除@Test注解; 但测试Runner不会报告这样的测试。 或者,你可以在@Test之前或之后添加@Ignore注解。 测试Runner将报告忽略测试的数量,以及运行的测试数量和失败的测试数量。
注意,如果要记录测试被忽略的原因,可以使用@Ignore的可选参数(一个字符串)
@Ignore("Test is ignored as a demonstration")
@Test
public void testSame() {
assertThat(1, is(1));
}
期望异常
你是如何验证代码是否按期望抛出异常? 验证代码正常完成很重要,但确保代码在特殊情况下按预期运行也至关重要。 例如:
new ArrayList<Object>().get(0);
该代码应该抛出一个IndexOutOfBoundsException。 @Test注解有一个可选参数“expected”,它作为Throwable的子类。 如果我们想验证ArrayList抛出正确的异常,我们会写:
@Test(expected = IndexOutOfBoundsException.class)
public void empty() {
new ArrayList<Object>().get(0);
}
应小心使用expected参数。 如果方法中的任何代码抛出IndexOutOfBoundsException异常,上述测试将通过。 对于较长的测试,建议使用如下描述的ExpectedException规则。
上述方法对简单的情况是有用的,但它也有其局限性。 例如,不能测试异常的消息值,或者异常抛出后域对象的状态。
Try/Catch
为了解决这个问题,你可以使用JUnit 3.x中的try / catch:
@Test
public void testExceptionMessage() {
try {
new ArrayList<Object>().get(0);
fail("Expected an IndexOutOfBoundsException to be thrown");
} catch (IndexOutOfBoundsException anIndexOutOfBoundsException) {
assertThat(anIndexOutOfBoundsException.getMessage(), is("Index: 0, Size: 0"));
}
}
期望异常规则
或者,使用ExpectedException规则。 此规则可以让你不仅指出你期望的是什么异常,还可以指出你期望的异常消息
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void shouldTestExceptionMessage() throws IndexOutOfBoundsException {
List<Object> list = new ArrayList<Object>();
thrown.expect(IndexOutOfBoundsException.class);
thrown.expectMessage("Index: 0, Size: 0");
list.get(0); // execution will never get past this line
}
期望消息还可以使用Matchers,这可以让你在测试中有更大的灵活性,例如:
thrown.expectMessage(Matchers.containsString("Size: 0"));
此外,可以你可以使用Matchers来检查异常,如果它具有要验证的嵌入状态,那么它将非常有用。例如:
import static org.hamcrest.Matchers.hasProperty;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.startsWith;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
public class TestExy {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void shouldThrow() {
TestThing testThing = new TestThing();
thrown.expect(NotFoundException.class);
thrown.expectMessage(startsWith("some Message"));
thrown.expect(hasProperty("response", hasProperty("status", is(404))));
testThing.chuck();
}
private class TestThing {
public void chuck() {
Response response = Response.status(Status.NOT_FOUND).entity("Resource not found").build();
throw new NotFoundException("some Message", response);
}
}
}
有关ExpectedException规则的延伸讨论,请参阅此博文。
测试超时
如果一个测试“失控”或花费太长时间,可能会自动失败。有两种可选方法可以实现此行为
在@Test注解上使用超时参数(应用于测试方法)
您可以选择以毫秒为单位指定超时,以使测试方法在超过该毫秒数的时间内失败。 如果超出时间限制,则会因为抛出异常而触发测试失败:
@Test(timeout=1000)
public void testWithTimeout() {
...
}
超时规则(应用于测试类的所有测试用例)
超时规则对类中的所有测试方法应用相同的超时
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.Timeout;
public class HasGlobalTimeout {
public static String log;
private final CountDownLatch latch = new CountDownLatch(1);
@Rule
public Timeout globalTimeout = Timeout.seconds(10); // 10 seconds max per method tested
@Test
public void testSleepForTooLong() throws Exception {
log += "ran1";
TimeUnit.SECONDS.sleep(100); // sleep for 100 seconds
}
@Test
public void testBlockForever() throws Exception {
log += "ran2";
latch.await(); // will block
}
}
Matchers and assertthat
Joe Walnes在JMock 1之上建立了一个新的断言机制。方法名称是assertThat,语法如下所示:
assertThat(x, is(3));
assertThat(x, is(not(4)));
assertThat(responseString, either(containsString("color")).or(containsString("colour")));
assertThat(myList, hasItem("3"));
通用语法:
assertThat([value], [matcher statement]);
此断言语法的优点包括:
更可读和可分类:
这种语法允许你根据主语,动词,对象(assert“x is 3”)来考虑
而不是assertEquals,它使用动词,对象,主语(assert“等于3 x”)
组合:Matchers语句可以是
否定(not(s))
组合((either(s).or(t))
映射到集合(each(s))
或者使用自定义组合(afterFiveSeconds(s))
比较:
assertTrue(responseString.contains("color") || responseString.contains("colour"));
// ==> failure message:
// java.lang.AssertionError:
assertThat(responseString, anyOf(containsString("color"), containsString("colour")));
// ==> failure message:
// java.lang.AssertionError:
// Expected: (a string containing "color" or a string containing "colour")
// got: "Please choose a font"
更多Matchers参考:Matchers
Parameterized
Parameterized测试可以使用不同参数多次运行同一个测试。
比如说测试斐波那契数列:
数列 | 值 |
---|---|
F ( 0 ) | 0 |
F ( 1 ) | 1 |
F ( 2 ) | 1 |
F ( 3 ) | 2 |
F ( 4 ) | 3 |
F ( 5 ) | 5 |
F ( 6 ) | 8 |
F ( n ) | F ( n - 1 ) + F ( n - 2 )( n >= 2,n ∈ N*) |
构造方法方式
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.Parameters;
@RunWith(Parameterized.class)
public class FibonacciTest {
@Parameters
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][] {
{ 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 }
});
}
private int fInput;
private int fExpected;
public FibonacciTest(int input, int expected) {
fInput= input;
fExpected= expected;
}
@Test
public void test() {
//fInput = 0,fExpected = 0 测试1次
//fInput = 1,fExpected = 1 测试1次
//fInput = 2,fExpected = 1 测试1次
//依次类推,一共测试 6 次
assertEquals(fExpected, Fibonacci.compute(fInput));
}
}
public class Fibonacci {
public static int compute(int n) {
int result = 0;
if (n <= 1) {
result = n;
} else {
result = compute(n - 1) + compute(n - 2);
}
return result;
}
}
属性注入方式
当前只能用于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
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 // first data value (0) is default
public /* NOT private */ int fInput;
@Parameter(1)
public /* NOT private */ int fExpected;
@Test
public void test() {
assertEquals(fExpected, Fibonacci.compute(fInput));
}
}
public class Fibonacci {
...
}
Rule
Rule允许在测试类中非常灵活地添加或重新定义每个测试方法的行为。 测试人员可以重复使用或扩展以Rule,也可以自行编写。
测试方法名规则
TestName Rule 可以在测试方法中使用当前测试方法名:
public class NameRuleTest {
@Rule
public final TestName name = new TestName();
@Test
public void testA() {
// name.getMethodName() 会获取当前测试方法名
assertEquals("testA", name.getMethodName());
}
@Test
public void testB() {
assertEquals("testB", name.getMethodName());
}
}
超时规则
Timeout Rule 对类中的所有测试方法应用相同的超时时间
public static class HasGlobalTimeout {
public static String log;
@Rule
public final TestRule globalTimeout = Timeout.millis(20);
@Test
public void testInfiniteLoop1() {
// 死循环,肯定会超时,报TestTimedOutException异常
log += "ran1";
for(;;) {}
}
@Test
public void testInfiniteLoop2() {
log += "ran2";
for(;;) {}
}
}
异常规则
ExpectedException Rules 允许对预期的异常类型和消息进行测试
public static class HasExpectedException {
@Rule
public final ExpectedException thrown = ExpectedException.none();
@Test
public void throwsNothing() {
}
@Test
public void throwsNullPointerException() {
thrown.expect(NullPointerException.class);
throw new NullPointerException();
}
@Test
public void throwsNullPointerExceptionWithMessage() {
thrown.expect(NullPointerException.class);
thrown.expectMessage("happened?");
thrown.expectMessage(startsWith("What"));
throw new NullPointerException("What happened?");
}
}
更多Rule或者自定义Rule参考 https://github.com/junit-team/junit4/wiki/Rules