前言:JUnit4引入了注解(Annotation)机制,通过解析注解就可以为测试提供相应的信息,并抛弃了JUnit3使用命名约束以及反射机制的方法。
建议:
- 不建议使用IDE集成的JUnit,因为其版本是固定的,相对而言可能一些新特性是不支持的。
- 测试代码放在与src同级的另外一个源文件夹中,方便测试代码的管理。
- 测试文件的路径结构应与被测试的源文件相同。
- 测试文件的命名格式为
Test+名称
,这样更加直观。 - JUnit4中初始化函数命名为
setUp()
,释放资源的函数命名为tearDown()
【这是为了与JUnit3兼容】。 - 为了在JUnit4中像JUnit3中直接使用
assert
[断言],我们要静态导入Assert
类(import static org.junit.Assert.*;
)
一、基础
1.1 Maven依赖
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
注意:JUnit自4.9之后引入了hamcrest-core依赖,因为其包含JUnit所需要的核心匹配器org.hamcrest.CoreMatchers
,如果你想使用org.hamcrest.Matchers
就需要引入hamcrest-library
依赖。
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-library</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
1.2 基础
- 定义一个测试类的要求是,这个类必须是
public
的并且包含了一个无参数的构造函数(如果没有定义其它构造函数,Java会为我们隐式地创建它)。 - 创建一个测试方法的要求是,这个方法必须使用@Test注解,并且是public的,不带任何参数,并且返回值为void类型。
- JUnit在调用(执行)每个@Test方法之前,为测试类创建一个新的实例。 这有助于提供测试方法之前的独立性,并且避免在测试代码中产生意外的副作用。因为每个测试方法都运行与一个新的测试类实例上,所以我们就不能在测试方法之间重用各个实例变量值。
1.3 Junit最佳实践
- 一次只测试一个对象
- 测试方法选择有意义的名字
- 在调用
assert
中解释失败的原因 - 一个单元测试等于一个
@Test
方法 - 测试任何可能失败的事务
- 让测试改善代码
- 使异常测试更易于阅读
- 相同的包,分离的目录
二、注解摘要
- 基本注解
@Test
:测试代码。@Ignore
:忽略的测试方法。@Before
:在每个测试之前运行的代码。@After
:在每个测试之后运行的代码。@BeforeClass
:针对所有测试,只执行一次,且必须为static void。@AfterClass
:针对所有测试,只执行一次,且必须为static void。
- 高级注解
@Rule
:允许灵活添加或重新定义测试类中的每个测试方法的行为。[Since:4.7]@ClassRule
:相对于@Rule,其针对的是类的行为。[Since:4.9]@FixMethodOrder
:指定测试方法的执行顺序。[Since:4.11]@RunWith
:指定测试类使用某个运行器。
@Suite.SuiteClasses
或@SuiteClasses
:指定Suite运行器时,用来指定其中包含的测试类。@Parameters
:指定测试类的测试数据集合,采用构造方法进行注入。@Parameter
:用来取代上面构造方法指定方式,指定注入的字段的索引。@Category
:将测试类或测试方法标记为属于一个或多个测试类别。值是任意类的数组。此注解仅由Categories运行器解释。@Categories.IncludeCategory
:可简化为@IncludeCategory
@Categories.ExcludeCategory
:可简化为@ExcludeCategory
一个测试类单元测试的执行顺序为:
@BeforeClass → @Before → @Test → @After → @AfterClass
每一个测试方法的调用顺序为:
@Before → @Test → @After
@Test
用于指定该方法为测试用例,通常直接用来标记方法即可,其包含两个可选参数:
expected
:指定测试应当抛出的异常类型timeout
:指定测试超时时间【单位是毫秒】
警告:虽然超时timeout
对于捕获和终止无限循环很有用,但不应视为确定性。下面的测试可能会失败,也可能不会失败,这取决于操作系统如何调度线程。
@Test(timeout=100)
public void sleep100() {
Thread.sleep(100);
}
线程安全警告:带有timeout
参数的测试方法运行时所处的线程与运行的@Before
和@After
方法所处的线程是不同的。与没有timeout
参数的相同测试方法进行比较时,对于不是线程安全的代码,这可能会产生不同的行为。可以考虑使用Timeout规则进行替代,其可以确保测试方法与@Before
和@After
方法在运行时处于相同线程中。
@Ignore
如果出于某种原因,您不希望测试失败,您只希望它被忽略,您暂时禁用一个测试。
注意:@Ignore
包含一个可选参数,如果您想记录一个测试被忽略的原因,可以指定其值。
示例:
@Ignore("Test is ignored as a demonstration")
@Test
public void testSame() {
assertThat(1, is(1));
}
三、测试执行顺序@FixMethodOrder
JUnit在设计上没有指定测试方法的执行顺序,到目前为止,这些方法只是按照反射API返回的顺序调用的,JUnit自4.11版本之后引入了@FixMethodOrder
注解,这样就可以指定其运行顺序了。
@FixMethodOrder
注解是在类上进行注释的,并提供了三种顺序:
@FixMethodOrder(MethodSorters.DEFAULT)
:默认的顺序,以确定但不可预期的顺序执行。@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");
}
}
四、Test fixtures
测试fixture是一组对象的固定状态,用作运行测试的基线。测试fixture的目的是确保有一个众所周知的、固定的环境来运行测试,以便结果是可重复的。fixture实例:
- Preparation of input data and setup/creation of fake or mock objects
- Loading a database with a specific, known set of data
- Copying a specific known set of files creating a test fixture will create a set of objects initialized to certain states.
JUnit提供了注解,这样测试类就可以在每次测试之前或之后运行fixture,或者只在类的所有测试方法之前和之后运行一次。
JUnit包含四个fixture注解:两个用于类级的fixture,两个用于方法级的。
- 类级别的:
@BeforeClass
和@AfterClass
- 方法级别的:
@Before
和@After
@BeforeClass
和@AfterClass
在类的所有方法执行前以及执行后分别执行。而@Before
和@After
在类的每个方法前后都会执行。
五、断言
JUnit提供了所有原始类型、对象和数组的重载断言方法。参数顺序是期望值,后面是实际值。[可选地] 第一个参数可以是在失败时输出的字符串消息。
注意:有一个稍微不同的断言是 assertThat
, 第一个是可选参数用来在失败时进行输出,接下来是实际值,最后是一个Matcher对象。需要注意的是期望值和实际值与其他断言是相反的。
常用方法 | 解释 |
---|---|
assertArrayEquals(expecteds, actuals) | 查看两个数组是否相等 |
assertEquals(expected, actual) | 查看两个对象是否相等,类似于字符串比较使用的equals()方法 |
assertNotEquals(first, second) | 查看两个对象是否不相等。 |
assertNull(object) | 查看对象是否为空 |
assertNotNull(object) | 查看对象是否不为空 |
assertSame(expected, actual) | 查看两个对象的引用是否相等。类似于使用“==”比较两个对象 |
assertNotSame(unexpected, actual) | 查看两个对象的引用是否不相等。类似于使用“!=”比较两个对象 |
assertTrue(condition) | 查看运行结果是否为true |
assertFalse(condition) | 查看运行结果是否为false |
assertThat(actual, matcher ) | 查看实际值是否满足指定的条件 |
fail() | 让测试失败 |
示例:
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
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);
}
}
六、junit3和junit4的使用区别:
- 在JUnit3中需要继承
TestCase
类,但在JUnit4中已经不需要继承TestCase
- 在JUnit3中需要覆盖
TestCase
中的setUp
和tearDown
方法,其中setUp
方法会在测试执行前被调用以完成初始化工作,而tearDown
方法则在结束测试结果时被调用,用于释放测试使用中的资源,而在JUnit4中,只需要在方法前加上@Before
,@After
注解 - 在JUnit3中对某个方法进行测试时,测试方法的命令是固定的,例如对addBook这个方法进行测试,需要编写名字为testAddBook的测试方法,而在JUnit4中没有方法命令的约束,在方法的前面加上@Test,这就代表这个方法是测试用例中的测试方法
- 新的断言
assertThat
- @BeforeClass 和 @AfterClass 。在JUnit3,如果所有的test case仅调用一次setUp()和tearDown()需要使用TestSetup类
- 测试异常处理@Test(expected = DataFormatException.class)
- 设置超时@Test(timeout = 1000)
- 忽略测试@Ignore
- 集成测试