追求代码质量 – JUnit 4与TestNG的比拟
当前位置:以往代写 > JAVA 教程 >追求代码质量 – JUnit 4与TestNG的比拟
2019-06-14

追求代码质量 – JUnit 4与TestNG的比拟

追求代码质量 – JUnit 4与TestNG的比拟

副标题#e#

颠末长时间努力的开拓之后,JUnit 4.0 于本年年头宣布了。JUnit 框架的 某些最有趣的变动 —— 出格是对付本专栏的读者来说 —— 正是通过巧妙地利用注释实现的。除外观和睦势气魄方面的显著改 进外,新框架的特性使测试用例的体例从布局法则中解放出来。使本来僵化的 fixture 模子更为机动,有利于采纳可设置水平更高的要领。因此,JUnit 框架 不再强求把每一项测试事情界说为一个名称以 test 开始的要领,而且此刻可以 只运行一次 fixture,而不是每次测试都需要运行一次。

固然这些改变 令人欣慰,但 JUnit 4 并不是第一个提供基于注释的机动模子的 Java™ 测试框架。在修改 JUnit 之前好久,TestNG 就已成立为一个基于注释的框架。

事实上,是 TestNG 在 Java 编程中率先 实现了操作注释举办测试,这 使它成为 JUnit 的有力竞争敌手。然而,自从 JUnit 4 宣布后,许多开拓者质 疑:二者之间尚有什么不同吗?在本月的专栏中,我将接头 TestNG 差异于 JUnit 4 的一些特性,并提议回收一些要领,使得这两个框架能继承相互增补, 而不是相互竞争。

您知道吗?

在 Ant 中运行 JUnit 4 测试比估量的要可贵多。事实上 ,一些团队已发明,惟一的办理要领是进级到 Ant 1.7。

外貌上的相似

JUnit 4 和 TestNG 有一些配合的重要特性。这两个框架都让测试事情 简朴得令人受惊(和愉快),给测试事情带来了便利。二者也都拥有活泼的社区 ,为主动开拓提供支持,同时生成富厚的文档。

两个框架的差异在于核 心设计。JUnit 一直 是一个单位测试框架,也就是说,其构建目标是促进单个 工具的测试,它确实可以或许极其有效地完成此类任务。而 TestNG 则是用来办理更 高 级此外测试问题,因此,它具有 JUnit 中所没有的一些特性。

一个简朴的测试用例

初看起来,JUnit 4 和 TestNG 中实现的测试很是相似。为了更好地领略我 的意思,请看一下清单 1 中的代码。这是一个 JUnit 4 测试,它有一个 macro-fixture(即仅在所有测试运行前挪用一次的 fixture),这个 macro- fixture 由 @BeforeClass 属性暗示:

清单 1. 一个简朴的 JUnit 4 测试用例

package test.com.acme.dona.dep;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import org.junit.BeforeClass;
import org.junit.Test;

public class DependencyFinderTest {
  private static DependencyFinder finder;

  @BeforeClass
  public static void init() throws Exception {
  finder = new DependencyFinder();
  }

  @Test
  public void verifyDependencies()
  throws Exception {
   String targetClss =
    "test.com.acme.dona.dep.DependencyFind";

   Filter[] filtr = new Filter[] {
    new RegexPackageFilter("java|junit|org")};

   Dependency[] deps =
    finder.findDependencies(targetClss, filtr);

   assertNotNull("deps was null", deps);
   assertEquals("should be 5 large", 5, deps.length);
  }
}

JUnit 用户会当即留意到:这个类中没有了以前版本的 JUnit 中所要求的一 些语法身分。这个类没有 setUp() 要领,也差池 TestCase 类举办扩展,甚至 也没有哪个要领的名称以 test 开始。这个类还操作了 Java 5 的一些特性,例 如静态导入,很明明地,它还利用了注释。


#p#副标题#e#

更多的机动性

在清单 2 中,您可以看到同一个 测试项目。不外这次是用 TestNG 实现的 。这里的代码跟清单 1 中的测试代码有个微妙的不同。发明白吗?

清单 2. 一个 TestNG 测试用例

package test.com.acme.dona.dep;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Configuration;
import org.testng.annotations.Test;

public class DependencyFinderTest {
  private DependencyFinder finder;

  @BeforeClass
  private void init(){
  this.finder = new DependencyFinder();
  }

  @Test
  public void verifyDependencies()
  throws Exception {
   String targetClss =
    "test.com.acme.dona.dep.DependencyFind";

   Filter[] filtr = new Filter[] {
    new RegexPackageFilter("java|junit|org")};

   Dependency[] deps =
    finder.findDependencies(targetClss, filtr);

   assertNotNull(deps, "deps was null" );
   assertEquals(5, deps.length, "should be 5 large");
  }
}

#p#分页标题#e#

显然,这两个清单很相似。不外,假如仔细看,您会发明 TestNG 的编码规 则比 JUnit 4 更机动。清单 1 里,在 JUnit 中我必需把 @BeforeClass 修饰 的要领声明为 static,这又要求我把 fixture,即 finder 声明为 static。我 还必需把 init() 声明为 public。看看清单 2,您就会发明差异。这里不再需 要那些法则了。我的 init() 要领既不是 static,也不是 public。

从最初起,TestNG 的机动性就是其主要优势之一,但这并非它惟一的卖点。 TestNG 还提供了 JUnit 4 所不具备的其他一些特性。

#p#副标题#e#

依赖性测试

JUnit 框架想到达的一个方针就是测试断绝。它的缺点是:人们很难确定测 试用例执行的顺序,而这对付任何范例的依赖性测试都很是重要。开拓者们利用 了多种技能来办理这个问题,譬喻,按字母顺序指定测试用例,或是更多地依靠 fixture 来适内地办理问题。

假如测试乐成,这些办理要领都没什么问题。可是,假如测试不乐成,就会 发生一个很贫苦的效果:所有 后续的依赖测试也会失败。在某些环境下,这会 使大型测试套件陈诉出很多不须要的错误。譬喻,假设有一个测试套件测试一个 需要登录的 Web 应用措施。您可以建设一个有依赖干系的要领,通过登录到这 个应用措施来建设整个测试套件,从而制止 JUnit 的断绝机制。这种办理要领 不错,可是假如登录失败,纵然登录该应用措施后的其他成果都正常事情,整个 测试套件依然会全部失败!

跳过,而不是标为失败

与 JUnit 差异,TestNG 操作 Test 注释的 dependsOnMethods 属性来应对 测试的依赖性问题。有了这个便利的特性,就可以轻松指定依赖要领。譬喻,前 面所说的登录将在某个要领之前 运行。另外,假如依赖要领失败,它将被跳过 ,而不是标志为失败。

清单 3. 利用 TestNG 举办依赖性测试

import  net.sourceforge.jwebunit.WebTester;

public class AccountHistoryTest {
  private WebTester tester;

  @BeforeClass
  protected void init() throws Exception {
  this.tester = new WebTester();
  this.tester.getTestContext(). 
   setBaseUrl("http://div.acme.com:8185/ceg/");
  }

  @Test
  public void verifyLogIn() {
  this.tester.beginAt("/");
  this.tester.setFormElement("username", "admin");
  this.tester.setFormElement("password", "admin");
  this.tester.submit();
  this.tester.assertTextPresent("Logged in as admin");
  }

  @Test (dependsOnMethods = {"verifyLogIn"})
  public void verifyAccountInfo() {
  this.tester.clickLinkWithText("History", 0);
  this.tester.assertTextPresent("GTG Data Feed");
  }
}

在清单 3 中界说了两个测试:一个验证登录,另一个验证账户信息。请留意 ,通过利用 Test 注释的 dependsOnMethods = {"verifyLogIn"} 子句, verifyAccountInfo 测试指定了它依赖 verifyLogIn() 要领。

通过 TestNG 的 Eclipse 插件(譬喻)运行该测试时,假如 verifyLogIn 测试失败,TestNG 将直接跳过 verifyAccountInfo 测试,请拜见图 1:

图 1. 在 TestNG 中跳过的测试

追求代码质量 - JUnit 4与TestNG的相比

对付大型测试套件,TestNG 这种不标志为失败,而只是跳过的处理惩罚要领可以 减轻许多压力。您的团队可以会合精神查找为什么百分之五十的测试套件被跳过 ,而不是去找百分之五十的测试套件失败的原因!更有利的是,TestNG 采纳了 只从头运行失败测试的机制,这使它的依赖性测试配置更为完善。

#p#副标题#e#

失败和重运行

在大型测试套件中,这种从头运行失败测试的本领显得尤为利便。这是 TestNG 独占的一个特性。在 JUnit 4 中,假如测试套件包罗 1000 项测试,其 中 3 项失败,很大概就会迫使您从头运行整个测试套件(修改错误今后)。不 用说,这样的事情大概会淹灭几个小时。

一旦 TestNG 中呈现失败,它就会建设一个 XML 设置文件,对失败的测试加 以说明。假如操作这个文件执行 TestNG 运行措施,TestNG 就只 运行失败的测 试。所以,在前面的例子里,您只需从头运行那三个失败的测试,而不是整个测 试套件。

#p#分页标题#e#

实际上,您可以通过清单 2 中的 Web 测试的例子本身看到这点。 verifyLogIn() 要领失败时,TestNG 自动建设一个 testng-failed.xml 文件。 该文件将成为如清单 4 所示的替代性测试套件:

清单 4. 失败测试的 XML 文件

<!DOCTYPE suite SYSTEM  "http://testng.org/testng-1.0.dtd">
<suite thread-count="5" verbose="1" name="Failed suite  [HistoryTesting]"
     parallel="false" annotations="JDK5">
  <test name="test.com.acme.ceg.AccountHistoryTest(failed)"  junit="false">
  <classes>
   <class name="test.com.acme.ceg.AccountHistoryTest">
   <methods>
    <include name="verifyLogIn"/>
   </methods>
   </class>
  </classes>
  </test>
</suite>

运行小的测试套件时,这个特性好像没什么大不了。可是假如您的测试套件 局限较大,您很快就会体会到它的长处。

参数化测试

TestNG 中另一个有趣的特性是参数化测试。在 JUnit 中,假如您想改变某 个受测要领的参数组,就只能给每个 差异的参数组编写一个测试用例。大都情 况下,这不会带来太多贫苦。然而,我们有时会遇到一些环境,对个中的业务逻 辑,需要运行的测试数目变革范畴很大。

在这样的环境下,利用 JUnit 的测试人员往往会转而利用 FIT 这样的框架 ,因为这样就可以用表格数据驱动测试。可是 TestNG 提供了开箱即用的雷同特 性。通过在 TestNG 的 XML 设置文件中放入参数化数据,就可以对差异的数据 集重用同一个测试用例,甚至有大概会获得差异的功效。这种技能完美地制止了 只能 假定一切正常的测试,或是没有对界线举办有效验证的环境。

在清单 5 中,我用 Java 1.4 界说了一个 TestNG 测试,该测试可吸收两个 参数:classname 和 size。这两个参数可以验证某个类的条理布局(也就是说 ,假如传入 java.util.Vector,则 HierarchyBuilder 所构建的 Hierarchy 的 值将为 2 )。

清单 5. 一个 TestNG 参数化测试

package  test.com.acme.da;

import com.acme.da.hierarchy.Hierarchy;
import com.acme.da.hierarchy.HierarchyBuilder;

public class HierarchyTest {
  /**
  * @testng.test
  * @testng.parameters value="class_name, size"
  */
  public void assertValues(String classname, int size) throws  Exception{
  Hierarchy hier = HierarchyBuilder.buildHierarchy (classname);
  assert hier.getHierarchyClassNames().length == size: "didn't  equal!";
  }
}

#p#副标题#e#

清单 5 列出了一个泛型测试,它可以回收差异的数据重复重用。请花点时间 思考一下这个问题。假如有 10 个差异的参数组合需要在 JUnit 中测试,您只 能写 10 个测试用例。每个测试用例完成的任务根基是沟通的,只是受测要领的 参数有所改变。可是,假如利用参数化测试,就可以只界说一个 测试用例,然 后,(举例来说)把所需的参数模式加到 TestNG 的测试套件文件中。清单 6 中展示了这中要领:

清单 6. 一个 TestNG 参数化测试套件文件

<!DOCTYPE suite  SYSTEM "http://beust.com/testng/testng-1.0.dtd">
<suite name="Deckt-10">
  <test name="Deckt-10-test">

  <parameter name="class_name" value="java.util.Vector"/>
  <parameter name="size" value="2"/>

  <classes>
   <class name="test.com.acme.da.HierarchyTest"/>
  </classes>
  </test>
</suite>

清单 6 中的 TestNG 测试套件文件只对该测试界说了一个参数组 (class_name 为 java.util.Vector,且 size 便是 2),但却具有无限的大概 。这样做的一个特另外长处是:将测试数据移动到 XML 文件的无代码工件就意 味着非措施员也可以指定命据。

高级参数化测试

尽量从一个 XML 文件中抽取数据会很利便,但偶然会有些测试需要有巨大类 型,这些范例无法用 String 或原语值来暗示。TestNG 可以通过它的 @DataProvider 注释处理惩罚这样的环境。@DataProvider 注释可以利便地把巨大参 数范例映射到某个测试要领。譬喻,清单 7 中的 verifyHierarchy 测试中,我 回收了重载的 buildHierarchy 要领,它可吸收一个 Class 范例的数据, 它断 言(asserting)Hierarchy 的 getHierarchyClassNames() 要领应该返回一个 适当的字符串数组:

清单 7. TestNG 中的 DataProvider 用法

#p#分页标题#e#

package  test.com.acme.da.ng;

import java.util.Vector;

import static org.testng.Assert.assertEquals;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import com.acme.da.hierarchy.Hierarchy;
import com.acme.da.hierarchy.HierarchyBuilder;

public class HierarchyTest {

  @DataProvider(name = "class-hierarchies")
  public Object[][] dataValues(){
  return new Object[][]{
   {Vector.class, new String[] {"java.util.AbstractList",
    "java.util.AbstractCollection"}},
   {String.class, new String[] {}}
  };
  }

  @Test(dataProvider = "class-hierarchies")
  public void verifyHierarchy(Class clzz, String[] names)
  throws Exception{
   Hierarchy hier = HierarchyBuilder.buildHierarchy(clzz);
   assertEquals(hier.getHierarchyClassNames(), names,
   "values were not equal");
  }
}

dataValues() 要领通过一个多维数组提供与 verifyHierarchy 测试要领的 参数值匹配的数据值。TestNG 遍历这些数据值,并按照数据值挪用了两次 verifyHierarchy。在第一次挪用时,Class 参数被配置为 Vector.class ,而 String 数组参数容纳 “java.util.AbstractList ” 和 “ java.util.AbstractCollection ” 这两个 String 范例的数据。这样挺利便吧 ?

为什么只选择其一?

我已经探讨了对我而言,TestNG 的一些独占优势,可是它尚有其他几个特性 是 JUnit 所不具备的。譬喻 TestNG 中利用了测试分组,它可以按照诸如运行 时间这样的特征来对测试分类。也可在 Java 1.4 中通过 javadoc 气势气魄的注释 来利用它,如 清单 5 所示。

正如我在本文开头所说,JUnit 4 和 TestNG 在外貌上是相似的。然而,设 计 JUnit 的目标是为了阐明代码单位,而 TestNG 的预期用途则针对高级测试 。对付大型测试套件,我们不但愿在某一项测试失败时就得从头运行数千项测试 ,TestNG 的机动性在这里尤为有用。这两个框架都有本身的优势,您可以随意 同时利用它们。

    关键字:

在线提交作业