JUnit 是 Java? 语言事实上的 标准单元测试库JUnit 是该库三年以来最具里程碑意义的一次发布它的新特性主要是通过采用 Java 中的标记(annotation)而不是利用子类反射或命名机制来识别测试从而简化测试在本文中执着的代码测试人员 Elliotte Harold 以 JUnit 为例详细介绍了如何在自己的工作中使用这个新框架注意本文假设读者具有 JUnit 的使用经验
JUnit 由 Kent Beck 和 Erich Gamma 开发几乎毫无疑问是迄今所开发的最重要的第三方 Java 库正如 Martin Fowler 所说在软件开发领域从来就没有如此少的代码起到了如此重要的作用JUnit 引导并促进了测试的盛行由于 JUnitJava 代码变得更健壮更可靠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 的知名度和市场占有份额它们都没有在诸如 AntMaven 或 Eclipse 之类的产品中具有广泛的开箱即用支持所以 Beck 和 Gamma 着手开发了一个新版本的 JUnit它利用 Java 的新特性(尤其是注释)的优势使得单元测试比起用最初的 JUnit 来说更加简单用 Beck 的话来说JUnit 的主题是通过进一步简化 JUnit鼓励更多的开发人员编写更多的测试JUnit 尽管保持了与现有 JUnit 测试套件的向后兼容但是它仍然承诺是自 JUnit 以来 Java 单元测试方面最重大的改进
注意该框架的改进是相当前沿的尽管 JUnit 的大轮廓很清晰但是其细节仍然可以改变这意味着本文是对 JUnit 抢先看而不是它的最终效果
测试方法
以前所有版本的 JUnit 都使用命名约定和反射来定位测试例如下面的代码测试 + 等于
import junitframeworkTestCase;
public class AdditionTest extends TestCase {
private int x = ;
private int y = ;
public void testAddition() {
int z = x + y;
assertEquals( z);
}
}
而在 JUnit 中测试是由 @Test 注释来识别的如下所示
import orgjunitTest;
import junitframeworkTestCase;
public class AdditionTest extends TestCase {
private int x = ;
private int y = ;
@Test public void testAddition() {
int z = x + y;
assertEquals( z);
}
}
使用注释的优点是不再需要将所有的方法命名为 testFoo()testBar()等等例如下面的方法也可以工作
import orgjunitTest;
import junitframeworkTestCase;
public class AdditionTest extends TestCase {
private int x = ;
private int y = ;
@Test public void additionTest() {
int z = x + y;
assertEquals( z);
}
}
下面这个方法也同样能够工作
import orgjunitTest;
import junitframeworkTestCase;
public class AdditionTest extends TestCase {
private int x = ;
private int y = ;
@Test public void addition() {
int z = x + y;
assertEquals( z);
}
}
这允许您遵循最适合您的应用程序的命名约定例如我介绍的一些例子采用的约定是测试类对其测试方法使用与被测试的类相同的名称例如ntains() 由 ntains() 测试Listadd() 由 ListTestaddAll() 测试等等
TestCase 类仍然可以工作但是您不再需要扩展它了只要您用 @Test 来注释测试方法就可以将测试方法放到任何类中但是您需要导入 junitAssert 类以访问各种 assert 方法如下所示
import orgjunitAssert;
public class AdditionTest {
private int x = ;
private int y = ;
@Test public void addition() {
int z = x + y;
AssertassertEquals( z);
}
}
您也可以使用 JDK 中新特性(static import)使得与以前版本一样简单
import static orgjunitAssertassertEquals;
public class AdditionTest {
private int x = ;
private int y = ;
@Test public void addition() {
int z = x + y;
assertEquals( z);
}
}
这种方法使得测试受保护的方法非常容易因为测试案例类现在可以扩展包含受保护方法的类了
SetUp 和 TearDown
JUnit 测试运行程序(test runner)会在运行每个测试之前自动调用 setUp() 方法该方法一般会初始化字段打开日志记录重置环境变量等等例如下面是摘自 XOM 的 XSLTransformTest 中的 setUp() 方法
protected void setUp() {
SystemsetErr(new PrintStream(new ByteArrayOutputStream()));
inputDir = new File(data);
inputDir = new File(inputDir xslt);
inputDir = new File(inputDir input);
}
在 JUnit 中您仍然可以在每个测试方法运行之前初始化字段和配置环境然而完成这些操作的方法不再需要叫做 setUp()只要用 @Before 注释来指示即可如下所示
@Before protected void initialize() {
SystemsetErr(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() {
SystemsetErr(new PrintStream(new ByteArrayOutputStream()));
}
清除方法与此类似在 JUnit 中您使用 tearDown() 方法该方法类似于我在 XOM 中为消耗大量内存的测试所使用的方法
protected void tearDown() {
doc = null;
Systemgc();
}
对于 JUnit 我可以给它取一个更自然的名称并用 @After 注释它
@After protected void disposeDocument() {
doc = null;
Systemgc();
}
与 @Before 一样也可以用 @After 来注释多个清除方法这些方法都在每个测试之后运行
最后您不再需要在超类中显式调用初始化和清除方法只要它们不被覆盖即可测试运行程序将根据需要自动为您调用这些方法超类中的 @Before 方法在子类中的 @Before 方法之前被调用(这反映了构造函数调用的顺序)@After 方法以反方向运行子类中的方法在超类中的方法之前被调用否则多个 @Before 或 @After 方法的相对顺序就得不到保证
套件范围的初始化
JUnit 也引入了一个 JUnit 中没有的新特性类范围的 setUp() 和 tearDown() 方法任何用 @BeforeClass 注释的方法都将在该类中的测试方法运行之前刚好运行一次而任何用 @AfterClass 注释的方法都将在该类中的所有测试都运行之后刚好运行一次
例如假设类中的每个测试都使用一个数据库连接一个网络连接一个非常大的数据结构或者还有一些对于初始化和事情安排来说比较昂贵的其他资源不要在每个测试之前都重新创建它您可以创建它一次并还原它一次该方法将使得有些测试案例运行起来快得多例如当我测试调用第三方库的代码中的错误处理时我通常喜欢在测试开始之前重定向 Systemerr以便输出不被预期的错误消息打乱然后我在测试结束后还原它如下所示
// This class tests a lot of error conditions which
// Xalan annoyingly logs to Systemerr This hides Systemerr
// before each test and rest