Java开源测试东西JUnit简介
当前位置:以往代写 > JAVA 教程 >Java开源测试东西JUnit简介
2019-06-14

Java开源测试东西JUnit简介

Java开源测试东西JUnit简介

副标题#e#

1.简介

在一篇早些的文章(请拜见Test Infected: Programmers Love Writing Tests, Java Report, July 1998, Volume 3, Number 7)中,我们描写了如何利用一个简朴的框架来编写可反复的测试。在本文中我们将仓皇一瞥其内中细节,并向你展示该框架自己是如何被结构的。

我们细致地研究JUint框架并思索如何来结构它。我们发明白很多差异条理上的教导。在本文中,我们将实验着立即与它们举办相同,这是一个令人绝望的任务,但至少它是在我们向你展示设计和结构一件代价被证实的软件的上下文中来举办的。

我们激发了一个关于框架方针的接头。在对框架自己的表达期间,方针将反复呈现很多小的细节中。从此,我们提出框架的设计和实现。设计将从模式(诧异,诧异)的角度举办描写,并作为美妙的措施来予以实现。我们总结了一些优秀的关于框架开拓的想法。

2.什么是JUnit的方针呢?

首先,我们不得不回到开拓的假定上去。假如缺少一个措施特性的自动测试(automated test),我们便假定其无法事情。这看起来要比主流的假定越发安详,主流的假定认为假如开拓者向我们担保一个措施特机可以或许事情,那么此刻和未来其城市永远事情。

从这个概念来看,当开拓者编写和调试代码时,它们的事情并没有完成,它们还要必需编写测试来演示措施可以或许事情。然而,每小我私家都太忙,他们要做的工作太多,他们没有富裕的时间用于测试。我已经有太多的代码需要编写,要我如何再来编写测试代码?答复我,强硬的项目司理先生。因此,首要方针就是编写一个框架,在这个框架中开拓者可以或许看到实际来编写测试的但愿之光。该框架必需要利用常见的东西,从而进修起来不会有太多的新对象。其不能比完全编写一个新测试所必需的事情更多。必需解除反复性的事情。

假如所有测试都这样去做的话,你将可以仅在一个调试器中编写表达式来完成。然而,这对付测试而言尚不充实。汇报我你的措施此刻可以或许事情,对我而言并没有什么辅佐,因为它并没有向我担保你的措施从我此刻集成之后的每一分钟都将会事情,以及它并没有向我担保你的措施将依然可以或许事情五年,当时你已经分开了很长的时间。

于是,测试的第二个方针就是生成可一连保持其代价的测试。除原作者以外的其他人必需可以或许执行测试并表明其功效。应该可以或许将差异作者的测试团结起来并在一起运行,而不必担忧彼此斗嘴。

最后,必需可以或许以现有的测试作为支点来生成新的测试。生成一个装置(setup)或夹具(fixture)是昂贵的,而且一个框架必需可以或许对夹具举办重用,以运行差异的测试。哦,尚有此外吗?

3.JUnit的设计

JUnit的设计将以一种首次在Patterns Generate Architectures(请拜见"Patterns Generate Architectures", Kent Beck and Ralph Johnson, ECOOP 94)中利用的气势气魄来泛起。其思想是通过从零开始来应用模式,然后一个接一个,直至你得到系统架构的方法来讲授一个系统的设计。我们将提出需要办理的架构问题,总结用来办理问题的模式,然后展示如何将模式应用于JUnit。

3.1 由此开始-TestCase

首先我们必需构建一个工具来表达我们的根基观念,TestCase(测试案例)。开拓者常常在脑子中存在着测试案例,但在实现它们的时候却回收了很多差异的方法-

· 打印语句

· 调试器表达式

· 测试剧本

假如我们想要轻松地哄骗测试,就必需将它们构建成工具。这将会获取到一个仅仅是埋没在开拓者脑子中的测试,并使之详细化,其支持我们建设测试的方针,即可以或许一连地保持它们的代价。同时,工具的开拓者较量习惯于利用工具来举办开拓,因此将测试构建成工具的抉择支持我们的方针-使测试的编写越发吸引人(或至少是不太富丽)。

Command(呼吁)模式(请拜见Gamma, E., et al. Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley, Reading, MA, 1995)则可以或许较量好地满意我们的需求。摘引其意图(intent),“将一个请求封装成一个工具,从而使你可用差异的请求对客户举办参数化;对请求举办列队或记录请求日志…”Command汇报我们可觉得一个操纵生成一个工具并给出它的一个“execute(执行)”要领。以下代码界说了TestCase类:

public abstract class TestCase implements Test {

}


#p#副标题#e#

因为我们期望可以通过担任来对该类举办重用,我们将其声明为“public abstract”。临时忽略其实现接口Test的事实。鉴于当前设计的需要,你可以将TestCase看作是一个孤独的类。

每一个TestCase在建设时都要有一个名称,因此若一个测试失败了,你便可识别出失败的是哪个测试。

#p#分页标题#e#

public abstract class TestCase implements Test {
  private final String fName;
  public TestCase(String name) {
   fName= name;
  }
  public abstract void run();
   …
}

为了叙述JUnit的演变进程,我们将利用图(diagram)来展示构架的快照(snapshot)。我们利用的标志很简朴。其利用包括相关模式的尖方框来标注类。当类在模式中的脚色(role)显而易见时,则仅显示模式的名称。假如脚色并不清晰,则在尖方框中增加与该类相关的参加者的名称。该标志可使图的杂乱度降到最小限度,并首次见诸于Applying Design Patterns in Java(请拜见Gamma, E., Applying Design Patterns in Java, in Java Gems, SIGS Reference Library, 1997)。图1展示了这种应用于TestCase中的标志。由于我们是在处理惩罚一个单独的类而且没有不明晰的处所,因此仅显示模式的名称。

Java开源测试对象JUnit简介

图1 TestCase应用Command

3.2 空缺填充-run()

接下来要办理的问题是给开拓者一个便捷的“处所”,用于安排他们的夹具代码和测试代码。将TestCase声明为abstract是指开拓者但愿通过子类化(subclassing)来对TestCase举办重用。然而,假如我们所有能作的就是提供一个只有一个变量且没有行为的超类,那么将无法做太多的事情来满意我们的首个方针-使测试更易于编写。

幸运的是,所有测试都具有一个配合的布局-成立一个测试夹具,在夹具上运行一些代码,查抄功效,然后清理夹具。这意味着每一个测试将与一个新的夹具一起运行,而且一个测试的功效不会影响到其它测试的功效。这支持测试代价最大化的方针。

Template Method(模板要领)较量好地涉及到我们的问题。摘引其意图,“界说一个操纵中算法的骨架,并将一些步调延迟到子类中。Template Method使得子类可以或许不改变一个算法的布局便可从头界说该算法的某些特定步调。”这完全得当。我们就是想让开拓者可以或许别离来思量如何编写夹具(成立和拆卸)代码,以及如何编写测试代码。不管奈何,这种执行的序次对付所有测试都将保持沟通,而不管夹具代码如何编写,或测试代码如何编写。

Template Method如下:

public void run() {
 setUp();
 runTest();
 tearDown();
}

#p#副标题#e#

这些要领被缺省实现为“什么都不做”:

protected void runTest() { }
protected void setUp() { }
protected void tearDown() { }

由于setUp和tearDown会被用来重写(override),并且其将由框架来举办挪用,因此我们将其声明为protected。我们的第二个快照如图2所示。

Java开源测试对象JUnit简介

图2 TestCase.run()应用Template Method

3.3 功效陈诉-TestResult

假如一个TestCase在丛林中运行,是否有人体贴其功效呢?虽然-你之所以运行测试就是为了要证实它们可以或许运行。测试运行完后,你想要一个记录,一个什么可以或许事情和什么未能事情的总结。

假如测试具有相等的乐成或失败的时机,可能假如我们方才运行一个测试,我们大概只是在TestCase工具中设定一个符号,而且当测试完毕时去看这个符号。然而,测试(往往)长短常不匀称的-他们凡是城市事情。因此我们只是想要记录失败,以及对乐成的一个高度浓缩的总结。

The Smalltalk Best Practice Patterns(请拜见 Beck, K. Smalltalk Best Practice Patterns, Prentice Hall, 1996)有一个可以合用的模式,称为Collecting Parameter(收集参数)。其发起当你需要在多个要领间举办功效收集时,应该在要领中增加一个参数,并通报一个工具来为你收集功效。我们建设一个新的工具,TestResult(测试功效),来收集运行的测试的功效。

public class TestResult extends Object {
 protected int fRunTests;
 public TestResult() {
  fRunTests= 0;
 }
}

这个简朴版本的TestResult仅仅可以或许计较所运行测试的数目。为了利用它,我们不得不在TestCase.run()要领中添加一个参数,并通知TestResult该测试正在运行:

public void run(TestResult result) {
 result.startTest(this);
 setUp();
 runTest();
 tearDown();
}

而且TestResult必需要记着所运行测试的数目:

public synchronized void startTest(Test test) {
fRunTests++;
}

#p#分页标题#e#

我们将TestResult的stratTest要领声明为synchronized,从而当测试运行在差异的线程中时,一个单独的TestResult可以或许安详地对功效举办收集。最后,我们想要保持TestCase简朴的外部接口,因此建设一个无参的run()版本,其认真建设本身的TestResult。

public TestResult run() {
  TestResult result= createResult();
  run(result);
  return result;
}
protected TestResult createResult() {
  return new TestResult();
}

我们下面的设计快照可如图3所示。

Java开源测试对象JUnit简介

图3 TestResult应用Collecting Parameter

#p#副标题#e#

假如测试老是可以或许正确运行,那么我们将没有须要编写它们。只有当测试失败时测试才是让人感乐趣的,尤其是当我们没有预期到它们会失败的时候。更有甚者,测试可以或许以我们所预期的方法失败,譬喻通过计较一个不正确的功效;可能它们可以或许以越发吸引人的方法失败,譬喻通过编写一个数组越界。无论测试奈何失败,我们都想执行后头的测试。

JUnit区分了失败(failures)和错误(errors)。失败的大概性是可预期的,而且以利用断言(assertion)来举办查抄。而错误则是不行预期的问题,如ArrayIndexOutOfBoundsException。失败可通过一个AssertionFailedError来发送。为了可以或许识别出一个不行预期的错误和一个失败,将在catch子句(1)中对失败举办捕捉。子句(2)则捕捉所有其它的异常,并确保我们的测试可以或许继承运行…

public void run(TestResult result) {
  result.startTest(this);
  setUp();
  try {
   runTest();
  }
  catch (AssertionFailedError e) { //1
   result.addFailure(this, e);
  }
  catch (Throwable e) { // 2
   result.addError(this, e);
  }
  finally {
   tearDown();
  }
}

TestCase提供的assert要了解触发一个AssertionFailedError。JUnit针对差异的目标提供一组assert要领。下面只是最简朴的一个:

protected void assert(boolean condition) {
if (!condition)
throw new AssertionFailedError();
}(【译者注】由于与JDK中的要害字assert斗嘴,在最新的JUnit宣布版本中此处的assert已经改为assertTrue。)

AssertionFailedError不该该由客户(TestCase中的一个测试要领)来认真捕捉,而应该由Template Method内部的TestCase.run()来认真。因此我们将AssertionFailedError派生自Error。

public class AssertionFailedError extends Error {
  public AssertionFailedError () {}
}

在TestResult中收集错误的要领可如下所示:

public synchronized void addError(Test test, Throwable t) {
  fErrors.addElement(new TestFailure(test, t));
}
public synchronized void addFailure(Test test, Throwable t) {
  fFailures.addElement(new TestFailure(test, t));
}

TestFailure是一个小的框架内部辅佐类(helper class),其将失败的测试和为后续陈诉发送信号的异常绑定在一起。

public class TestFailure extends Object {
  protected Test fFailedTest;
  protected Throwable fThrownException;
}

类型形式的Collecting parameter模式要求我们将Collecting parameter通报给每一个要领。假如我们遵循该发起,每一个测试要领都将需要TestResult的参数。其将会造成这些要领签名(signature)的“污染”。利用异常来发送失败可以作为一个友善的副浸染,使我们可以或许制止这种签名的污染。一个测试案例要领,或一个其所挪用的辅佐要领(helper method),可在不必知道TestResult的环境下抛出一个异常。作为一个学习质料,这里给出一个简朴的测试要领,其来自于我们MoneyTest套件(【译者注】请拜见JUnit宣布版本中附带的别的一篇文章JUnit Test Infected: Programmers Love Writing Tests)。其演示了一个测试要领是如何不必知道任何干于TestResult的信息的。

public void testMoneyEquals() {
  assert(!f12CHF.equals(null));
  assertEquals(f12CHF, f12CHF);
  assertEquals(f12CHF, new Money(12, "CHF"));
  assert(!f12CHF.equals(f14CHF));
}(【译者注】由于与JDK中的要害字assert斗嘴,在最新的JUnit宣布版本中此处的assert已经改为assertTrue。)

#p#副标题#e#

JUnit提出了关于TestResult的差异实现。其缺省实现是对失败和错误的数目举办计数并收集功效。TextTestResult收集功效并以一种文本的形式来表达它们。最后,JUnit Test Runner的图形版本则利用UITestResult来更新图形化的测试状态。

#p#分页标题#e#

TestResult是框架的一个扩展点(extension point)。客户可以或许自界说它们的TestResult类,譬喻HTMLTestResult可将功效上报为一个HTML文档。  3.4 不愚蠢的子类-再论TestCase

我们已经应用Command来表示一个测试。Command依赖于一个单独的像execute()这样的要领(在TestCase中称为run())来对其举办挪用。这个简朴接口答允我们可以或许通过沟通的接口来挪用一个command的差异实现。

我们需要一个接口对我们的测试举办一般性地运行。然而,所有的测试案例都被实现为沟通类的差异要领。这制止了不须要的类扩散(proliferation of classes)。一个给定的测试案例类(test case class)可以实现很多差异的要领,每一个要领界说了一个单独的测试案例(test case)。每一个测试案例都有一个描写性的名称,如testMoneyEquals或testMoneyAdd。测试案例并不切合简朴的command接口。沟通Command类的差异实例需要与差异的要领来被挪用。因此我们下面的问题就是,使所有测试案例从测试挪用者的角度上看都是沟通的。

回首当前可用的设计模式所涉及的问题,Adapter(适配器)模式便映入脑海。Adapter具有以下意图“将一个类的接口转换成客户但愿的别的一个接口”。这听起来很是适合。Adapter汇报我们差异的这样去做的方法。个中之一即是class adapter(类适配器),其利用子类化来对接口举办适配。譬喻,为了将testMoneyEquals适配为runTest,我们实现了一个MoneyTest的子类并重写runTest要领来挪用testMoneyEquals。

public class TestMoneyEquals extends MoneyTest {
public TestMoneyEquals() { super("testMoneyEquals"); }
protected void runTest () { testMoneyEquals(); }
}

利用子类化需要我们为每一个测试案例都实现一个子类。这便给测试者安排了一个特另外承担。这有悖于JUnit的方针,即框架应该尽大概地使测试案例的增加变得简朴。另外,为每一个测试要领建设一个子类会造成类膨胀(class bloat)。很多类将仅具有一个单独的要领,这种开销不值得,并且很难会提出有意义的名称。

Java提供了匿名内部类(anonymous inner class),其提供了一个让人感乐趣的Java所专门的方案来办理类的定名问题。通过匿名内部类我们可以或许建设一个Adapter而不必缔造一个类的名称:

TestCase test= new MoneyTest("testMoneyEquals ") {
protected void runTest() { testMoneyEquals(); }
};

这与完全子类化对比要便捷很多。其是以开拓者的一些承担作为价钱以保持编译时期的范例查抄(compile-time type checking)。Smalltalk Best Practice Pattern描写了别的的方案来办理差异实例的问题,这些实例是在配合的pluggable behavior(插件式行为)标题下的差异表示。该思想是利用一个单独的参数化的类来执行差异的逻辑,而无需举办子类化。

Pluggable behavior的最简朴形式是Pluggable Selector(插件式选择器)。Pluggable Selector在一个实例变量中生存了一个Smalltalk的selector要领。该思想并不范围于Smalltalk,其也合用于Java。在Java中并没有一个selector要领的标志。可是Java reflection(反射) API答允我们可以按照一个要领名称的暗示字符串来挪用该要领。我们可以利用该种特性来实现一个Java版的pluggable selector。岔开话题而言,我们凡是不会在泛泛的应用措施中利用反射。在我们的案例中,我们正在处理惩罚的是一个基本设施框架,因此它可以戴上反射的帽子。

JUnit可以让客户自行选择,是利用pluggable selector,或是实现上面所提到的匿名adapter类。正因如此,我们提供pluggable selector作为runTest要领的缺省实现。在该环境下,测试案例的名称必需要与一个测试要领的名称相一致。如下所示,我们利用反射来对要领举办挪用。首先我们会查找Method工具。一旦我们有了method工具,便会挪用它并通报其参数。由于我们的测试要领没有参数,所以我们可以通报一个空的参数数组。

protected void runTest() throws Throwable {
Method runMethod= null;
try {
runMethod= getClass().getMethod(fName, new Class[0]);
} catch (NoSuchMethodException e) {
assert("Method \""+fName+"\" not found", false);
}
try {
runMethod.invoke(this, new Class[0]);
}
// catch InvocationTargetException and IllegalAccessException
}

JDK1.1的reflection API仅答允我们发明public的要领。基于这个原因,你必需将测试要领声明为public,不然将会获得一个NoSuchMethodException异常。

在下面的设计快照中,添加进了Adapter和Pluggable Selector。

Java开源测试对象JUnit简介

图4 TestCase应用Adapter(与一个匿名内部类一起)或Pluggable Selector

#p#副标题#e#

3.5 不必体贴一个或多个-TestSuit

#p#分页标题#e#

为了得到对系统状态的信心,我们需要运行很多测试。到此刻为止,JUnit可以或许运行一个单独的测试案例并在一个TestResult中陈诉功效。我们接下来的挑战是要对其举办扩展,以使其可以或许运行很多差异的测试。当测试挪用者不必体贴其运行的是一个或多个测试案例时,这个问题便可以或许轻松地办理。可以或许在该环境下渡过难关的一个风行模式就是Composite(组合)。摘引其意图,“将工具组合成树形布局以暗示‘部门-整体’的条理布局。Composite使得用户对单个工具和组合工具的利用具有一致性。”在这里‘部门-整体’的条理布局是让人感乐趣的处所。我们想支持可以或许层层相套的测试套件。

Composite引入如下的参加者:

· Component:声明我们想要利用的接口,来与我们的测试举办交互。

· Composite:实现该接口并维护一个测试的荟萃。

· Leaf:代表composite中的一个测试案例,其切合Component接口。

该模式汇报我们要引入一个抽象类,来为单独的工具和composite工具界说民众的接口。这个类的根基意图就是界说一个接口。在Java中应用Composite时,我们更倾向于界说一个接口,而非抽象类。利用接口制止了将JUnit提交成一个详细的基类来用于测试。所必须的是这些测试要切合这个接口。因此我们对模式的描写举办变通,并引入一个Test接口:

public interface Test {
public abstract void run(TestResult result);
}

TestCase对应着Composite中的一个Leaf,而且实现了我们上面所看到的这个接口。

下面,我们引入参加者Composite。我们将其取名为TestSuit(测试套件)类。TestSuit在一个Vector中生存了其子测试(child test):

public class TestSuite implements Test {
private Vector fTests= new Vector();
}

run()要领对其子成员举办委托(delegate):

public void run(TestResult result) {
for (Enumeration e= fTests.elements(); e.hasMoreElements(); ) {
Test test= (Test)e.nextElement();
test.run(result);
}
}

Java开源测试对象JUnit简介

图5 TestSuit应用Composite

最后,客户必需能将测试添加到一个套件中,它们将利用addTest要领来这样做:

public void addTest(Test test) {
fTests.addElement(test);
}

留意所有上面的代码是如何仅对Test接口举办依赖的。由于TestCase和TestSuit两者都切合Test接口,我们可以递归地将测试套件再组合成套件。所有开拓者都可以或许建设他们本身的TestSuit。我们可建设一个组合了这些套件的TestSuit来运行它们所有的。

#p#副标题#e#

下面是一个建设TestSuit的示例:

public static Test suite() {
TestSuite suite= new TestSuite();
suite.addTest(new MoneyTest("testMoneyEquals"));
suite.addTest(new MoneyTest("testSimpleAdd"));
}

这会很好地事情,但它需要我们手动地将所有测试添加到一个套件中。早期的JUnit回收者汇报我们这样是愚蠢的。只要你编写一个新的测试案例,你就必需记取要将其添加到一个static的suit()要领中,不然其将不会运行。我们添加了一个TestSuit的便捷结构要领,该结构要领将测试案例类作为一个参数。其意图是提取(extract)测试要领,并建设一个包括这些测试要领的套件。测试要领必需遵循的简朴的约定是,以前缀“test”开头且不带参数。便捷结构要领就利用该约定,通过利用反射发明测试要领来结构测试工具。利用该结构要领,以上代码将会简化为:

public static Test suite() {
return new TestSuite(MoneyTest.class);
}

当你只是想运行测试案例的一个子集时,则最初的方法将依然有用。

3.6 总结

此刻我们位于JUnit走马观花的最后。通过模式的角度来叙述JUnit的设计,可如下图所示。

Java开源测试对象JUnit简介

图6 JUnit模式总结

留意TestCase作为框架抽象的中心,其是如何与四个模式举办相关的。成熟的工具设计的描写展示了这种沟通的“模式密度”。设计的中心是一个富厚的干系荟萃,这些干系与所支持的参加者(player)彼此关联。

#p#分页标题#e#

这是别的一种对待JUnit中所有模式的方法。在这个情节图板(storyboard)上,依次对每个模式的影响举办抽象地暗示。于是,Command模式建设了TestCase类,Template Method模式建设了run要领,等等。(情节图板的标志是在图6中标志的基本上删除了所有的文字)。

Java开源测试对象JUnit简介

图7 JUnit模式的情节图板

关于情节图板有一点要留意的是,图的巨大性是如安在我们应用Composite时举办跃迁的。其以图示的方法证实了我们的直觉,即Composite是一个强大的模式,但它会“使得图变得巨大。”因此应该审慎地予以利用。

4 结论

最后,让我们作一些全面的调查:

· 模式

我们发明从模式的角度来阐述设计长短常名贵的,无论是在我们举办框架的开拓中,照旧我们试图向其他人阐述它时。你此刻正处于一个完美的位置来鉴定,以模式来描写一个框架是否有效。假如你喜欢上面的阐述,请为你本身的系统实验沟通的表示气势气魄。

· 模式密度

TestCase周围的模式“密度”较量高,其是JUnit的要害抽象。高模式密度的设计越发易于利用,但却越发难于修改。我们发明像这样一个在要害抽象周围的高模式密度,对付成熟的框架而言是常见的。其对立面则应合用于那些不成熟的框架-它们应该具有低模式密度。一旦你发明你所要真正办理的问题,你便可以或许开始“浓缩(compress)”这个办理方案,直到一个模式越来越麋集的区域,而这些模式在个中提供了杠杆的浸染。

· 用本身做的对象

一旦我们完成了根基的单位测试成果,我们自身就要将其应用起来。TestCase可以验证框架可以或许为错误,乐成和失败陈诉正确的功效。我们发明跟着框架设计的继承演变,这是无价的。我们发明JUnit的最具挑战性的应用即是测试其自己的行为。

· 交集(intersection),而非并集(union)

在框架开拓中有一个诱惑就是,包括每一个你所可以或许具有的特性。究竟,你想使框架尽大概得有代价。然而,会有一种阻碍-开拓者不得不来抉择利用你的框架。框架所具有的特性越少,那么学起来就越容易,开拓者利用它的大概性就越大。JUnit即是按照这种气势气魄写就的。其仅实现了那些测试运行所完全根基的特性-运行测试的套件,使各个测试的执行互相彼此断绝,以及测试的自动运行。是的,我们无法抵挡对付一些特性的添加,可是我们会小心地将其放到它们本身的扩展包中(test.extensions)。该包中有一个值得留意的成员是TestDecorator,其答允在一个测试之前和之后可以执行附加的代码。

· 框架编写者要读他们的代码

我们花在阅读JUnit的代码上的时间比起编写它的时间要多出许多。并且花在去除反复成果上的时间险些与添加新成果的时间相等。我们努力地举办设计上的尝试,以多种我们可以或许想出的差异方法来添加新的类以及移动职责。通过对JUnit一连不绝地洞察(测试,工具设计,框架开拓),以及颁发更深入的文章的时机,我们因为我们的偏执而得到了回报(并将依然得到回报)。

Junit的最新版本可从ftp://www.armaties.com/D/home/armaties/ftp/TestingFramework/JUnit/下载。

    关键字:

在线提交作业