副标题#e#
JUnit 是 Java 语言事实上的 尺度单位测试库。JUnit 4 是该库三年以来最具里程碑意义的一次宣布。它的新特性主要是通过回收 Java 5 中的标志(annotation)而不是操作子类、反射或定名机制来识别测试,从而简化测试。在本文中,执着的代码测试人员 Elliotte Harold 以 JUnit 4 为例,具体先容了如安在本身的事情中利用这个新框架。留意,本文假设读者具有 JUnit 的利用履历。
JUnit 由 Kent Beck 和 Erich Gamma 开拓,险些毫无疑问是迄今所开拓的最重要的第三方 Java 库。正如 Martin Fowler 所说,“在软件开拓规模,从来就没有如此少的代码起到了如此重要的浸染”。JUnit 引导并促进了测试的流行。由于 JUnit,Java 代码变得更结实,更靠得住,bug 也比以前更少。JUnit(它自己的灵感来自 Smalltalk 的 SUnit)衍生了很多 xUnit 东西,将单位测试的优势应用于各类语言。nUnit (.NET)、pyUnit (Python)、CppUnit (C++)、dUnit (Delphi) 以及其他东西,影响了各类平台和语言上的措施员的测试事情。
然而,JUnit 仅仅是一个东西罢了。真正的优势来自于 JUnit 所回收的思想和技能,而不是框架自己。单位测试、测试先行的编程和测试驱动的开拓并非都要在 JUnit 中实现,任何较量 GUI 的编程都必需用 Swing 来完成。JUnit 自己的最后一次更新差不多是三年以前了。尽量它被证明比大大都框架更结实、更耐久,可是也发明白 bug;而更重要的是,Java 不绝在成长。Java 语言此刻支持泛型、列举、可变长度参数列表和注释,这些特性为可重用的框架设计带来了新的大概。
JUnit 的裹足不前并没有被那些想要废弃它的措施员所打败。挑战者包罗 Bill Venners 的 Artima SuiteRunner 以及 Cedric Beust 的 TestNG 等。这些库有一些可圈可点的特性,可是都没有到达 JUnit 的知名度和市场占有份额。它们都没有在诸如 Ant、Maven 或 Eclipse 之类的产物中具有遍及的开箱即用支持。所以 Beck 和 Gamma 着手开拓了一个新版本的 JUnit,它操作 Java 5 的新特性(尤其是注释)的优势,使得单位测试比起用最初的 JUnit 来说越发简朴。用 Beck 的话来说,“JUnit 4 的主题是通过进一步简化 JUnit,勉励更多的开拓人员编写更多的测试。”JUnit 4 尽量保持了与现有 JUnit 3.8 测试套件的向后兼容,可是它仍然理睬是自 JUnit 1.0 以来 Java 单位测试方面最重大的改造。
留意:该框架的改造是相当前沿的。尽量 JUnit 4 的大表面很清晰,可是其细节仍然可以改变。这意味着本文是对 JUnit 4 抢先看,而不是它的最终结果。
测试要领
以前所有版本的 JUnit 都利用定名约定和反射来定位测试。譬喻,下面的代码测试 1+1 便是 2:
import junit.framework.TestCase;
public class AdditionTest extends TestCase {
private int x = 1;
private int y = 1;
public void testAddition() {
int z = x + y; assertEquals(2, z);
}
}
而在 JUnit 4 中,测试是由 @Test 注释来识此外,如下所示:
import org.junit.Test;
import junit.framework.TestCase;
public class AdditionTest extends TestCase {
private int x = 1;
private int y = 1;
@Test public void testAddition() {
int z = x + y;
assertEquals(2, z);
}
}
利用注释的利益是不再需要将所有的要领定名为 testFoo()、testBar(),等等。譬喻,下面的要领也可以事情:
import org.junit.Test;
import junit.framework.TestCase;
public class AdditionTest extends TestCase {
private int x = 1;
private int y = 1;
@Test public void additionTest() {
int z = x + y; assertEquals(2, z);
}
}
下面这个要领也同样可以或许事情:
import org.junit.Test;
import junit.framework.TestCase;
public class AdditionTest extends TestCase {
private int x = 1;
private int y = 1;
@Test public void addition() {
int z = x + y; assertEquals(2, z);
}
}
#p#副标题#e#
这答允您遵循最适合您的应用措施的定名约定。譬喻,我先容的一些例子回收的约定是,测试类对其测试要领利用与被测试的类沟通的名称。譬喻,List.contains() 由 ListTest.contains() 测试,List.add() 由 ListTest.addAll() 测试,等等。
TestCase 类仍然可以事情,可是您不再需要扩展它了。只要您用 @Test 来注释测试要领,就可以将测试要领放到任何类中。可是您需要导入 junit.Assert 类以会见各类 assert 要领,如下所示:
import org.junit.Assert;
public class AdditionTest {
private int x = 1;
private int y = 1;
@Test public void addition() {
int z = x + y;
Assert.assertEquals(2, z);
}
}
您也可以利用 JDK 5 中新特性(static import),使得与以前版本一样简朴:
#p#分页标题#e#
import static org.junit.Assert.assertEquals;
public class AdditionTest {
private int x = 1;
private int y = 1;
@Test public void addition() {
int z = x + y; assertEquals(2, z);
}
}
这种要领使得测试受掩护的要领很是容易,因为测试案例类此刻可以扩展包括受掩护要领的类了。
SetUp 和 TearDown
JUnit 3 测试运行措施(test runner)会在运行每个测试之前自动挪用 setUp() 要领。该要领一般会初始化字段,打开日志记录,重置情况变量,等等。譬喻,下面是摘自 XOM 的 XSLTransformTest 中的 setUp() 要领:
protected void setUp() {
System.setErr(new PrintStream(new ByteArrayOutputStream()));
inputDir = new File("data");
inputDir = new File(inputDir, "xslt");
inputDir = new File(inputDir, "input");
}
在 JUnit 4 中,您仍然可以在每个测试要领运行之前初始化字段和设置情况。然而,完成这些操纵的要领不再需要叫做 setUp(),只要用 @Before 注释来指示即可,如下所示:
@Before protected void initialize() {
System.setErr(new PrintStream(new ByteArrayOutputStream()));
inputDir = new File("data");
inputDir = new File(inputDir, "xslt");
inputDir = new File(inputDir, "input");
}
甚至可以用 @Before 来注释多个要领,这些要领都在每个测试之前运行:
@Before protected void findTestDataDirectory() {
inputDir = new File("data");
inputDir = new File(inputDir, "xslt");
inputDir = new File(inputDir, "input");
}
@Before protected void redirectStderr() {
System.setErr(new PrintStream(new ByteArrayOutputStream()));
}
排除要领与此雷同。在 JUnit 3 中,您利用 tearDown() 要领,该要领雷同于我在 XOM 中为耗损大量内存的测试所利用的要领:
protected void tearDown() {
doc = null;
System.gc();
}
对付 JUnit 4,我可以给它取一个更自然的名称,并用 @After 注释它:
@After protected void disposeDocument()
{
doc = null;
System.gc();
}
与 @Before 一样,也可以用 @After 来注释多个排除要领,这些要领都在每个测试之后运行。
最后,您不再需要在超类中显式挪用初始化和排除要领,只要它们不被包围即可,测试运行措施将按照需要自动为您挪用这些要领。超类中的 @Before 要领在子类中的 @Before 要领之前被挪用(这反应告终构函数挪用的顺序)。@After 要领以反偏向运行:子类中的要领在超类中的要领之前被挪用。不然,多个 @Before 或 @After 要领的相对顺序就得不到担保。
套件范畴的初始化
JUnit 4 也引入了一个 JUnit 3 中没有的新特性:类范畴的 setUp() 和 tearDown() 要领。任何用 @BeforeClass 注释的要领都将在该类中的测试要领运行之前恰好运行一次,而任何用 @AfterClass 注释的要领都将在该类中的所有测试都运行之后恰好运行一次。
譬喻,假设类中的每个测试都利用一个数据库毗连、一个网络毗连、一个很是大的数据布局,可能尚有一些对付初始化和工作布置来说较量昂贵的其他资源。 不要在每个测试之前都从头建设它,您可以建设它一次,并还原它一次。该要领将使得有些测试案例运行起来快得多。譬喻,当我测试挪用第三方库的代码中的错误 处理惩罚时,我凡是喜欢在测试开始之前重定向 System.err,以便输出不被预期的错误动静打乱。然后我在测试竣事后还原它,如下所示:
// This class tests a lot of error conditions, which
// Xalan annoyingly logs to System.err. This hides System.err
// before each test and restores it after each test.
private PrintStream systemErr;
@BeforeClass protected void redirectStderr() {
systemErr = System.err;
// Hold on to the original value
System.setErr(new PrintStream(new ByteArrayOutputStream()));
}
@AfterClass protected void tearDown() {
// restore the original value
System.setErr(systemErr);
}
没有须要在每个测试之前和之后都这样做。可是必然要小心看待这个特性。它有大概会违反测试的独立性,并引入非预期的杂乱。假如一个测试在某种水平上改变了 @BeforeClass 所初始化的一个工具,那么它有大概会影响其他测试的功效。它有大概在测试套件中引入顺序依赖,并埋没 bug。与任何优化一样,只在分解和基准测试证明您具有实际的问题之后才实现这一点。这就是说,我看到了不止一个测试套件运行时间如此之长,以至不能像它 所需要的那样常常运行,尤其是那些需要成立许多网络和数据库毗连的测试。(譬喻,LimeWire 测试套件运行时间高出两小时。)要加速这些测试套件,以便措施员可以越发常常地运行它们,您可以做的就是淘汰 bug。
测试异常
#p#分页标题#e#
异常测试是 JUnit 4 中的最大改造。旧式的异常测试是在抛出异常的代码中放入 try 块,然后在 try 块的末端插手一个 fail() 语句。譬喻,该要领测试被零除抛出一个 ArithmeticException:
public void testDivisionByZero() {
try {
int n = 2 / 0;
fail("Divided by zero!");
}
catch (ArithmeticException success) {
assertNotNull(success.getMessage());
}
}
该要领不只丢脸,并且试图挑战代码包围东西,因为不管测试是通过照旧失败,总有一些代码不被执行。在 JUnit 4 中,您此刻可以编写抛出异常的代码,并利用注释来声明该异常是预期的:
@Test(expected=ArithmeticException.class)
public void divideByZero() { int n = 2 / 0;}
假如该异常没有抛出(可能抛出了一个差异的异常),那么测试就将失败。可是假如您想要测试异常的具体动静或其他属性,则仍然需要利用旧式的 try-catch 样式。
被忽略的测试
也许您有一个测试运行的时间很是地长。不是说这个测试应该运行得更快,而是说它所做的事情从基础上较量巨大或迟钝。需要会见长途网络处事器的测试通 常都属于这一类。假如您不在做大概会间断该类测试的工作,那么您大概想要跳过运行时间长的测试要领,以缩短编译-测试-调试周期。可能也许是一个因为超出 您的节制范畴的原因而失败的测试。譬喻,W3C XInclude 测试套件测试 Java 还不支持的一些 Unicode 编码的自动识别。不必总是被迫盯住那些赤色海浪线,这类测试可以被注释为 @Ignore,如下所示:
// Java doesn't yet support
// the UTF-32BE and UTF32LE encodings @Ignore
public void testUTF32BE() throws ParsingException, IOException, XIncludeException {
File input = new File( "data/xinclude/input/UTF32BE.xml" );
Document doc = builder.build(input);
Document result = XIncluder.resolve(doc);
Document expectedResult = builder.build( new File(outputDir, "UTF32BE.xml") );
assertEquals(expectedResult, result);
}
测试运行措施将不运行这些测试,可是它会指出这些测试被跳过了。譬喻,当利用文本界面时,会输出一个“I”(代表 ignore),而不是为通过的测试输出所经验的时间,也不是为失败的测试输出“E”:
$ java -classpath .:junit.jar org.junit.runner.JUnitCore nu.xom.tests.XIncludeTestJUnit version 4.0rc1.....I..Time: 1.149OK (7 tests)
可是必然要小心。最初编写这些测试大概有必然的原因。假如永远忽略这些测试,那么它们期望测试的代码大概会间断,而且这样的间断大概不能被检测到。忽略测试只是一个权宜之计,不是任何问题的真正办理方案。
时间测试
测试机能是单位测试最为疾苦的方面之一。JUnit 4 没有完全办理这个问题,可是它对这个问题有所辅佐。测试可以用一个超时参数来注释。假如测试运行的时间高出指定的毫秒数,则测试失败。譬喻,假如测试耗费 高出半秒时间去查找以前配置的一个文档中的所有元素,那么该测试失败:
@Test(timeout=500)
public void retrieveAllElementsInDocument() {
doc.query("//*");
}
除了简朴的基准测试之外,时间测试也对网络测试很有用。在一个测试试图毗连到的长途主机或数据库宕机或变慢时,您可以忽略该测试,以便不阻塞所有其 他的测试。好的测试套件执行得足够快,以至措施员可以在每个测试产生重大变革之后运行这些测试,有大概一天运行几十次。配置一个超时使得这一点越发可行。 譬喻,假如理会 http://www.ibiblio.org/xml 耗费了高出 2 秒,那么下面的测试就会超时:
@Test(timeout=2000)
public void remoteBaseRelativeResolutionWithDirectory()
throws IOException, ParsingException {
builder.build("http://www.ibiblio.org/xml");
}
新的断言
JUnit 4 为较量数组添加了两个 assert() 要领:
public static void assertEquals(Object[] expected, Object[] actual)public static void assertEquals(String message, Object[] expected, Object[] actual)
这两个要领以最直接的方法较量数组:假如数组长度沟通,且每个对应的元素沟通,则两个数组相等,不然不相等。数组为空的环境也作了思量。
需要增补的处所
JUnit 4 根基上是一个新框架,而不是旧框架的进级版本。JUnit 3 开拓人员大概会找到一些本来没有的特性。
#p#分页标题#e#
最明明的删节就是 GUI 测试运行措施。假如您想在测试通过期看到赏心好看的绿色海浪线,可能在测试失败时看到令人焦急的赤色海浪线,那么您需要一个具有集成 JUnit 支持的 IDE,好比 Eclipse。不管是 Swing 照旧 AWT 测试运行措施都不会被进级或绑缚到 JUnit 4 中。
下一个惊喜是,失败(assert 要领检测到的预期的错误)与错误(异常指出的非预期的错误)之间不再有任何不同。尽量 JUnit 3 测试运行措施仍然可以区别这些环境,而 JUnit 4 运行措施将不再可以或许区分。
最后,JUnit 4 没有 suite() 要领,这些要领用于从多个测试类构建一个测试套件。相反,可变长参数列表用于答允将不确定命量的测试通报给测试运行措施。
我对消除了 GUI 测试运行措施并不感想太兴奋,可是其他变动好像有大概增加 JUnit 的简朴性。只要思量有几多文档和 FAQ 当前专门用于表明这几点,然后思量对付 JUnit 4,您不再需要表明这几点了。
编译和运行 JUnit 4
当前,还没有 JUnit 4 的库版本。假如您想要体验新的版本,那么您需要从 SourceForge 上的 CVS 常识库获取它。分支(branch)是“Version4”(拜见 参考资料)。留意,许多的文档没有进级,仍然是指以旧式的 3.x 方法干事。Java 5 对付编译 JUnit 4 是必须的,因为 JUnit 4 大量用到注释、泛型以及 Java 5 语言级的其他特性。
自 JUnit 3 以来,从呼吁行运行测试的语法产生了一点变革。您此刻利用 org.junit.runner.JUnitCore 类:
$ java -classpath
.:junit.jar org.junit.runner.JUnitCore
TestA TestB TestC...JUnit version 4.0rc1Time: 0.003OK (0 tests)
兼容性
Beck 和 Gamma 尽力维持向前和向后兼容。JUnit 4 测试运行措施可以运行 JUnit 3 测试,不消做任何变动。只要将您想要运行的每个测试的全限定类名通报给测试运行措施,就像针对 JUnit 4 测试一样。运行措施足够智能,可以判别出哪个测试类依赖于哪个版本的 JUnit,并适内地挪用它。
向后兼容要坚苦一些,可是也可以在 JUnit 3 测试运行措施中运行 JUnit 4 测试。这一点很重要,所以诸如 Eclipse 之类具有集成 JUnit 支持的东西可以处理惩罚 JUnit 4,而不需要更新。为了使 JUnit 4 测试可以运行在 JUnit 3 情况中,可以将它们包装在 JUnit4TestAdapter 中。将下面的要领添加到您的 JUnit 4 测试类中应该就足够了:
public static junit.framework.Test suite() {
return new JUnit4TestAdapter(AssertionTest.class);
}
可是由于 Java 较量多变,所以 JUnit 4 一点都不向后兼容。JUnit 4 完全依赖于 Java 5 特性。对付 Java 1.4 或更早版本,它将不会编译或运行。
前景
JUnit 4 远没有竣事。许多重要的方面没有提及,包罗大部门的文档。我不推荐此刻就将您的测试套件转换成注释和 JUnit 4。纵然如此,开拓仍在快速举办,而且 JUnit 4 前景很是看好。尽量 Java 2 措施员在可预见的将来仍然需要利用 JUnit 3.8,可是那些已经转移到 Java 5 的措施员则应该很快思量使他们的测试套件适合于这个新的框架,以便匹配。