好象貌似有本书是用写一个JUnit作为例子来讲解TDD的。要说TDD绝对是个好东西,不过TDD本身并不能保证搞出好软件。这不,Junit就是个活生生的例子呀。
一直以来,我写Junit+Easymock测试都是这么来的:
public class SomeTest extends TestCase {
private final IMocksControl control = EasyMock.createStrictControl();
private final Connection connection = control.createMock(Connection.class);
@Override protected void tearDown() {
control.verify();
// other cleanups.
}
public void test1() {
expect(connection.createStatement()).andReturn(null);
...
control.replay();
...
}
}
不要细抠具体的mock用法,我这都是临时编的。但是这个测试类有两个肥肠严重的问题。说起来我一直都是这么做的,真是愧对付给我工资的那些老板们啊!
第一个问题:
JUnit里面不应该用field initializer的——或者说,不能用构造函数来初始化test fixture的。当调用new TestSuite(SomeTest.class)的时候,JUnit系统会对每一个testSomething()函数调用一次构造函数,生成一个独立的test instance. 这样做,保证了每个测试的独立性,最大程度地避免了某个test case影响其它test case的出现。
不过,“等等”,你说。这不是正好就对了么?每个test case都会调用一遍这些field initializer,没有任何问题了呀?
是呀,所以我才这么多年一直理直气壮,厚颜无耻地用这个idiom呀,用这个idiom。直到最近,同事中的java专家告诉我,这是不安全地,不道德地,是低级趣味地!
因为JUnit在没有执行test case,只是构造test suite的时候,就调用了这些构造函数,这就造成了几个额外问题:
1。这造成TestSuite的创建不够安全,有可能出现异常——我们希望异常只出现在test case运行的时候。并且TestSuite的创建速度也有可能受到影响。
2。我们无法保证一个TestCase只被运行一次。比如说RepeatedTest就会多次运行一个TestCase。所以保证TestCase至少看上去是immutable的就比较重要了。
第二个问题:
不应该在tearDown()里面调用verify()。如果test case失败了,首先这个verify()就没有调用的必要了。不应该用tearDown()来强制调用它。其次,如果verify()也失败(相当有可能地),那么后一个exception会冲掉前面的那个(JUnit 4里面修好了这个bug),而前面那个exception才是你真正需要的呀。
而且,如果verify()失败了,那么后面的other cleanups也被跳过去了,而它们才是最最重要的呀。
你说可气不可气?我还以为JUnit这么简单的东西,肯定不会用错的呢。
但是,让我不许定义final field,让我把所有的初始化都放在setUp里面,让我在tearDown里面一个接一个地套try-finally,这,这不是代码难看的问题,这是太没人性了!
我想啊想,终于顿悟了。这不是人民内部矛盾,而是不可调和的阶级冲突啊。
JUnit不同于TestNG, 它极度强调测试的隔离性,为此不惜禁止我们在一个测试类中共享一些有用的信息(比如,一个parse好的xml数据之类),每个test method都将使用一个单独的instance。可是,如此代价换来的居然还是一个不干不嘎的局面:我们还是不能假设一个instance只被用一次。它肯定不会被两个不同的test method使用地,——但是它可以被一个test method重复使用啊。哈,哈,哈。没想到吧?
到了JUnit 4, tearDown的问题解决了。我可以在每个@After函数中释放各自的资源,框架会保证它们都被执行。不过,我们还是不能在@After的函数中调用verify(), 还是因为对于verify()我们并不希望在测试本身失败的时候还调用它。
至于构造函数问题,没有任何改善,没有。同样,JUnit 4也没有正视广大人民群众对共享数据的呼声。不管TestSetup还是@BeforeClass都是要求你用evil static field。
忍无可忍之下,我又怒了。于是自己写了一个AjooTestSuite,偷偷从TestNG搞来了几个我一直非常眼馋的annotation: @BeforeTest, @AfterTest, @BeforeSuite, @AfterSuite, @ExceptionExpected。又自己填了两个@Verify和@Shared。前者用来在测试没出问题的情况下做一个公用的verify,比如verify mock object;后者用来在test case之间共享数据。一个使用AjooTestSuite的测试类可以这么写:
public class SomeTest extends TestCase {
public static Test suite() {
return new AjooTestSuite(SomeTest.class);
}
// 哈哈。终于可以用final和initializer了!
private final IMocksControl control = EasyMock.createStrictControl();
private final Connection conn = control.createMock(Connection.class);
@Verify
public void verifyMocks() {
control.verify();
}
@Shared
public static XmlObject provideXml() {
// ... read xml file and parse.
return xmlObj;
}
private final XmlObj;
// the shared xmlObj will be injected for each test case.
public SomeTest(String name, XmlObject obj) {
super(name);
this.xmlOj = obj;
}
@BeforeSuite
public static void initialize() {
// some suite level initialization. No more TestSetup!
}
@AfterSuite
public static void deinitialize() {
// suite level deinitialization.
}
@BeforeTest
public void setupMocks() {
expect(conn.createStatement()).andReturn(null);
}
@ExceptionExpected(NullPointerException.class)
public void test1() {
throw new NullPointerException();
}
}
写完之后,我这个得意呀。终于不用忍受JUnit屎一样的限制了。构造函数只有在测试运行的时候才调用,换句话说,这回test case是绝对immutable的了,绝对线程安全。耶!
除此之外,还加上了一些流行的annotation。@BeforeTest, @AfterTest, @BeforeSuite, @AfterSuite,@ExceptionExpected不用说,都跟TestNG一样的语义。@Verify用来搞类似verify mock之类的事情;@Shared标注的静态函数在每次test suite执行的时候会被调用一次,返回的数据会被保存并被注射给每一个test case instance。
完美呀,完美。这样一来,也不用转移到TestNG了,也不用忍受一些工具集成的问题升级到JUnit4了。就是一个新的TestSuite而已,100%向后兼容。
可是,事实证明,我说的太早了。我的java专家同事又给我泼了一瓢凉水:
现在,你在Eclipse/Intellij里面点运行,没问题,它会知道去寻找suite()函数,然后用你的自定义test suite。可是,如果你然后点某一个单独的test case, 比如test1, 然后说"run"。会发生什么哩?嘿嘿,它不再去找你的suite()函数啦,啦,啦,啦,啦(回声逐渐消失)。它直接跑到你的类里面去找这个函数来调用了。Surprise!
天啊。该死的Eclipse, 它难道不会调用suite(), 然后在suite()里面找么?
可仔细想想,又不是IDE的错。即使它调用suite()又如何?没有一个TestSuite.getTest(String name)的API供它调用啊。实际上,JUnit的TestCase也并不强制保证每个Test都有一个id的。
于是,我三天的工作白费了。呜呜呜!
总而言之,言而总之,千言万语,咬牙切齿汇成一句话:JUnit Sucks!
分享到:
相关推荐
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage,包含依赖包:junit-jupiter-5.5.1.jar,junit-jupiter-engine-5.5.1.jar,junit-jupiter-params-5.5.1.jar,junit-platform-launcher-1.5.1.jar,junit-...
Files contained in junit4-4.8.2.jar: LICENSE.txt META-INF/MANIFEST.MF junit.extensions.ActiveTestSuite.class junit.extensions.RepeatedTest.class junit.extensions.TestDecorator.class junit.extensions...
这个名为"junit5.jar"的文件正是JUnit 5的核心库,它包含了执行单元测试所需的所有核心组件,包括JUnit Platform、JUnit Jupiter和JUnit Vintage。本文将全面解析JUnit 5的关键特性和使用方法。 首先,JUnit ...
junit4.1junit4.1junit4.1junit4.1junit4.1
"JUnit in Action 3rd Edition" JUnit是一种流行的Java单元测试框架,由Kent Beck和Eric Gamma于1997年创立。JUnit在软件测试领域中扮演着重要的角色,帮助开发者编写高质量的代码。下面是关于JUnit的重要知识点: ...
JUnit API JUnit API JUnit API JUnit API JUnit API
Junit5是Java开发中最常用的单元测试框架之一,它的出现为开发者提供了更加高效、灵活的测试体验。相较于之前的版本,Junit5引入了许多新的特性和改进,使得测试代码的编写和维护变得更加简单。本整合包包含了Junit5...
JUnit是Java编程语言中最常用的单元测试框架之一,它允许开发者编写可重复运行的测试用例,以确保代码的正确性和稳定性。JUnit3.8.1是该框架的一个较早版本,尽管现在已经有更新的版本(如JUnit5),但了解其基本...
JUnit是Java编程语言中最常用的单元测试框架之一,用于编写和运行可重复的自动化测试用例。Junit5.7.2版本是这个框架的一个稳定版本,提供了许多改进和新特性,使得测试更加高效且易于维护。这个离线jar文件包含了...
JUnit5是Java编程语言中最流行的单元测试框架之一,它的最新版本带来了许多改进和新特性,使得测试更加高效和灵活。本资源包含的`junit5.jar`是JUnit5的运行库,可以用于运行使用JUnit5编写的测试用例。而`junit5-...
JUnit5相关的 JUnit Jupiter.jar依赖包,主要包含: junit-jupiter-5.5.1.jar,junit-jupiter-api-5.5.1.jar,junit-jupiter-engine-5.5.1.jar,junit-jupiter-params-5.5.1.jar,junit-jupiter-api-5.4.2.jar,...
JUnit5是Java编程语言中最广泛使用的单元测试框架之一,它为开发者提供了强大的测试能力,确保代码的质量和稳定性。本篇文章将详细介绍JUnit5的核心组件、主要功能以及如何在项目中使用。 JUnit5由三个主要模块组成...
《JUnit in Action》和《JUnit Recipes》是两本关于Java单元测试的重要书籍,它们深入浅出地介绍了如何使用JUnit框架进行高效、可靠的测试。JUnit是一个流行的开源测试框架,广泛用于Java应用程序的单元测试,它提供...
JUnit是Java编程语言中最常用的单元测试框架之一,它允许开发者编写可执行的测试用例来验证代码的功能。这里我们关注的是`junit-4.12.jar`版本,这是一个重要的里程碑,因为它带来了许多改进和新特性。让我们深入...
JUnit是Java编程语言中最常用的单元测试框架之一,它允许开发者编写可执行的测试用例来验证代码的功能。这个“junit工具jar包”是JUnit 4.8.1版本,一个稳定且广泛使用的版本,提供了丰富的断言方法和测试注解,便于...
JUnit是Java编程语言中最常用的单元测试框架之一,它允许开发者编写可执行的测试用例来验证代码的功能。单元测试是对程序中的最小可测试部分——通常是一个函数或方法——进行检查,确保其按照预期工作。JUnit作为...
JUnit是Java编程语言中最常用的单元测试框架之一,它极大地简化了对代码进行验证和调试的过程。Junit4.12是该框架的一个稳定版本,它包含了许多改进和新特性,使得测试更加灵活和强大。在本文中,我们将深入探讨...
JUnit4是Java编程语言中最广泛使用的单元测试框架之一,它为开发者提供了编写可重复执行、易于维护的测试代码的能力。这个“junit4 jar包”包含了运行JUnit4测试所必需的库文件,主要包括两个核心组件:`junit-4.11....
JUnit 5是JUnit系列的最新版本,它引入了模块化设计,包括JUnit Platform、JUnit Jupiter和JUnit Vintage三个核心组件。JUnit Platform提供了运行测试的基础架构,JUnit Jupiter则包含编写和执行测试的核心API,而...
赠送jar包:junit-4.11.jar; 赠送原API文档:junit-4.11-javadoc.jar; 赠送源代码:junit-4.11-sources.jar; 赠送Maven依赖信息文件:junit-4.11.pom; 包含翻译后的API文档:junit-4.11-javadoc-API文档-中文...