原文:http://www.javaworld.com/jw-12-2000/jw-1221-junit.html

考虑一下地区因素

考虑一下,如果在测试中用到日期。创建日期的方法可能是这样的:

Date date = DateFormat.getInstance ().parse ("dd/mm/yyyy");

很遗憾,那样的代码可能在另外一台在不同地区的机器上就不能正常运作。因此,这样写可能会更好:

Calendar cal = Calendar.getInstance ();
Cal.set (yyyy, mm-1, dd);
Date date = Calendar.getTime ();

第二种方法在运行换将地区不同的情况下更有兼容性。

使用JUnit的assert和fail方法,以及异常处理,可以让你的测试代码看上去更干净

许多JUnit的初学者喜欢用看上去可能更精巧的try和catch来捕捉异常,然后在catch中标记测试失败了。以下是一个例子:

public void exampleTest () {
   try {
       // do some test
   } catch (SomeApplicationException e) {
       fail ("Caught SomeApplicationException exception");
   }
}

实际上,JUnit会自动捕捉异常。它会把那些你没在测试代码中捕捉到的异常识别为错误,也就是说以上的实例中有许多多余的代码。
以下是一段更简单的代码,但它能完成同样的事:

public void exampleTest () throws SomeApplicationException {
   // do some test
}

在这个例子中,多余的代码被去除了,这样一来代码就更容易阅读和维护了。
广泛地使用多种assert方法来表达你的意图,而不是像这样:

assert (creds == 3);

应该像这样:

assertEquals ("The number of credentials should be 3", 3, creds);

以上这个例子对要读这段代码的人会更有帮助。而且如果这个断言失败了,它能提供给测试人员更多的信息。JUnit也支持浮点数的比较:

assertEquals ("some message", result, expected, delta);

当你在比较浮点数的时候,这个方法能帮助你更加快速地计算出实际结果和期望结果之间的差值。
使用assertSame()测试的是两个引用是不是指向同一个对象。使用assertEquals()来测试两个对象是不是完全相等。

在javadoc中编写测试文档

用word来写测试计划通常会有很多问题,而且写起来也很单调。这些用word敲出来的文档还需要和单元测试保持同步,这样就让测试流程更加复杂。如果可能的话,用javadoc来写测试文档会是一个较好的方案,这样也能确保所有的测试计划和用例都储存在一个地方。

尽量避免视觉检查的用例

在测试servlets、用户界面、或者其他一些会有比较复杂的输出的程序时,通常都会用到视觉检查的方法。视觉检查——就是由人类来检查输出的数据正确与否——需要耐心,抓取大量信息的能力,以及对细节的专注力,而这些优点在大多数人身上都是很难找到的。以下这些基本的技术会帮助你在进行测试时尽量避免视觉检查。

Swing

当你在测试一个基于Swing的用户界面时,你可以写一些用例:

  • 所有的component都呆在正确的panel里
  • 正确配置了样式管理项
  • 文本组件的字体正确

你可以在这里找到更多深入的相关的例子。

XML

当你在测试一些处理XML的类时,可能会花上很多时间用眼睛去查看XML文件的DOM是否和预期中的完全一致。其实你可以事先定义一个正确的DOM,然后和真正的输出做一个文本即可。

Servlets

你可以有许多方法来测试Servlets。你可以写一个虚拟的servlet框架并在测试之前事先配置好。在这个框架中需要包含正常环境中的一部分类。通过在虚拟的框架中来调用这些类来完成测试。

比如:

  • 可以继承HttpServletRequest来写测试类,指定header,方法,路径信息和其他一些数据。
  • 可以继承HttpServletResponse来写测试类,允许按时返回一个被测试类所期待的输出。

一个更简单的方案是使用HttpUnit来测试servlets。HttpUnit提供一个DOM视图来显示发送请求的结果,这样就能比较简单地来比较真实的数据和预期的结果。

你可以用很多方法来避免视觉检查。然而,有些时候,使用视觉检查或者是某些专门的测试工具会来得更省。比如说,用JUnit来测试用户界面的动态行为尽管是可能的,但是会很复杂。从数量庞大的记录/回放测试工具中挑一个买回来用用可能是个更好的主意,或者,去做一些少量的视觉检查也不无不可。然而,这不意味着在通常情况下,你可以忽视别用视觉检查的忠告。

执行整个系统上的所有测试不应该花上几个小时的时间。实际情况是,开发人员会更喜欢去执行那些跑起来很快的测试。如果不经常去执行系统上的所有测试,确保系统在某些更改后仍能正常运行就会非常困难。缺陷会悄无声息地潜入系统。

使用Reflection驱动的JUnit的API

允许TestSuite使用reflection,以此来减少维护时间。Reflection可以使你不必为任何新加的测试用例去更新整个suite()。

建一个用来测试整个系统的测试用例

建一个针对整个系统的测试用例相当有必要。如果有一个测试用例能覆盖整个系统,开发人员就能通过那个用例来测试自己的改动是否会影响到这系统中的任何细小之处。如果有这样一个测试,我们就能及早发现那些代码更新所带来的不易察觉的缺陷。如果没有这么一个测试,开发人员通常只会愿意去测试他们刚改动过的那个类。而且,执行所有的测试就会是件谁都不愿干的痛苦的体力活。

如果我们建了一个针对整个系统的测试,它将包括所有的已定义的测试用例。可以用这些测试用例来定义suite()的方法,这样一来,针对整个系统的一个测试套件就建成了。如果你有许多测试用例,建立这么一个测试套件可能会花掉很多时间。另外,如果有新的测试用例,或者已定义的那些测试用例需要重命名或者用不到了,那你也必须去更新整个测试套件。如果你不想手动去建立和维护这样一个测试套件,而是想让所有的测试用例自己就能生成这么一个套件。这些被引用的测试用例需要符合以下条件:

  • 它不能是自行载入的,因为那样会造成递归。所以我们需要确保测试用例是不可载入的。
  • 在类中不能载入那些从TestCase继承下来的类,也不能直接运行。
  • 需要区分单元测试和其他的测试,比如载入或压力测试。别让不同的测试在一次执行测试的时候全部跑完。
  • 接下来要介绍的测试工具类会递归地去查找当前目录下的所有文件,找到那些测试用例并把它们加到测试套件中来。

我们可以用Java的继承方式去定义我们的测试用例究竟属于哪个测试类别。我们可以让我们的那些测试用例分别从UnitTest,StressTest,LoadTest等等继承过来。不过,这样一来,那些测试用例就很难在不同的测试类别中重用,因为测试的类别是在很靠近基类的地方就定义下来的;它应该是在每个子类中才定义的。另一种方法,我们可以用这么一个字段来区别不同的测试类别:public final String TEST_ALL_TEST_TYPE。这样一来,只有在这个字段和启动自动化测试时配置的那个测试类别参数相匹配的时候,这个测试类的用例才会被执行。我们可以用三个类来实现这种方法:

  • ClassFinder可以递归地去寻找当前目录下的所有类。所有被找到的类都载入到运行环境中,并拿到所有完整的类名。把这些类名加到一个列表中,以待在之后的过程中载入它们。
  • TestCaseLoader会去载入刚才拿到的那个列表中的类,并判断这是不是一个测试用例。如果是的话,再加入到另一个测试用例的列表中。
  • TestAll是从TestCase继承过来的一个子类,并实现了suite()的方法,来载入TestCaseLoader所生成的测试用例列表中的那些类。

让我们来具体看看这些类的实现。

ClassFinder

ClassFinder是用来查找系统中的那些类的(包括测试对象和测试用例),它们都被储存在一个目录下。ClassFinder会找到目录下的所有类,并在之后的测试过程中使用。ClassFinder具体实现的第一部分如下:

public class ClassFinder {
   // The cumulative list of classes found.
   final private Vector classNameList = new Vector ();
   /**
     * Find all classes stored in classfiles in classPathRoot
     * Inner classes are not supported.
     */
   public ClassFinder(final File classPathRoot) throws IOException {
       findAndStoreTestClasses (classPathRoot);
   }
   /**
     * Recursive method that adds all class names related to classfiles it finds in
     * the currentDirectory (and below).
     */
   private void findAndStoreTestClasses (final File currentDirectory) throws IOException {
       String files[] = currentDirectory.list();
       for(int i = 0;i < files.length;i++) {
           File file = new File(currentDirectory, files[i]);
           String fileBase = file.getName ();
           int idx = fileBase.indexOf(".class");
           final int CLASS_EXTENSION_LENGTH = 6;
           if(idx != -1 && (fileBase.length() - idx) == CLASS_EXTENSION_LENGTH) {

在以上的代码中,我们迭代查找了目录下的所有文件。如果该文件的扩展名为“.class”,我们就把该文件的类名储存起来,就像这样:

JcfClassInputStream inputStream = new JcfClassInputStream(new FileInputStream (file));
JcfClassFile classFile = new JcfClassFile (inputStream);
System.out.println ("Processing: " + classFile.getFullName ().replace ('/','.'));
classNameList.add (classFile.getFullName ().replace ('/','.'));

这段代码用到了JCF包来载入类文件,并得到这些类文件的类名。JCF包是一个用来载入和检查类文件的工具包。(更多信息点)。JCF包可以帮我们得到所有类的完整类名。我们虽然可以从文件名来臆测类名,但是如果在某种环境下并不把类名当文件名保存起来事情就不好办了。如果是内部类的话也不能这么实现:

最后,我们检查文件是否为目录(请看下面的代码段)。如果是,我们就递归查找到该目录下,这样我就能拿到一个目录下的所有类了。

           } else if(file.isDirectory()) {
               findAndStoreTestClasses (file);
           }
       }
   }
   /**
     * Return an iterator over the collection of classnames (Strings)
     */

    public Iterator getClasses () {
       return classNameList.iterator ();
   }
}

TestCaseLoader

TestCaseLoader从刚才ClassFinder得到的那些类中得到测试类。以下的代码段展示了一些找到这些测试用例的上层代码:

public class TestCaseLoader {
   final private Vector classList = new Vector ();
   final private String requiredType;
   /**
     * Adds testCaseClass to the list of classdes
     * if the class is a test case we wish to load. Calls
     * shouldLoadTestCase () to determine that.
     */
   private void addClassIfTestCase (final Class testCaseClass) {
       if (shouldAddTestCase (testCaseClass)) {
           classList.add (testCaseClass);
       }
   }
   /**
     * Determine if we should load this test case. Calls isATestCaseOfTheCorrectType
     * to determine if the test case should be
     * added to the class list.
     */
   private boolean shouldAddTestCase (final Class testCaseClass) {
       return isATestCaseOfTheCorrectType (testCaseClass);
   }

你可以在下面找到一个叫isATestCaseOfTheCorrectType()的方法。它会去检查列表中的每个类:

  • 检查它是否是从TestCase继承来的。如果不是,那它就不是测试用例。
  • 检查该类中的TEST_ALL_TEST_TYPE和该类成员变量requiredType是否一致。

相关文章:JUnit最佳实践[译][一]JUnit最佳实践[译][三]

分享到: