CS5040 中级数据结构与算法

Chapter 3 Programming Tutorials

| 关于   «  9. JUnit 测试与你(JUnit Testing And You)   ::   目录   ::   11. JUnit 中的代码覆盖率(Code Coverage In JUnit)  »

10. 编写 JUnit 测试(Writing JUnit Tests)

10.1. 编写 JUnit 测试(Writing JUnit Tests)

我们已经回顾了测试代码的良好设计,以及如何设计模块化且 有意义的测试代码。本教程将展示对一棵 二叉搜索树进行的一系列基本测试。

在开始之前,请确保你已经熟悉 Junit/student.TestCase classes 的基础知识。

在本练习中,我将使用一棵由 V.S. Adamchik 编写的 BST。

Here is the full BST source code。

虽然这个类包含许多方法,但本教程只会测试其中几个。

import student.TestCase;

public class TestBST extends TestCase{
   private BST<Integer> bst;
   
   public void setUp() {
      bst = new BST<Integer>();
   }
   
   public void testAdd() {
      bst.insert(new Integer(8));//We see this tree on the Wikipedia as an example of a bst 
      bst.insert(new Integer(3));//We will use it to test our insert function since we
      bst.insert(new Integer(1));//know how the traversal at the end should work. There
      bst.insert(new Integer(6));//are other ways to do this, however, this structure should 
      bst.insert(new Integer(4));//provide us not only with a series of inserts that will hit
      bst.insert(new Integer(7));//every line of code in the insert method and produce results
      bst.insert(new Integer(10));//that we can expect
      bst.insert(new Integer(14));
      bst.insert(new Integer(13));
      bst.insert(new Integer(13));//Handle inserting an element that already exists
      bst.preOrderTraversal();
      assertFuzzyEquals("8 3 1 6 4 7 10 14 13", systemOut().getHistory());
      //systemOut().getHistory() returns all information printed to the terminal
      //We use fuzzy equals as there may be leading/following whitespace and
      //we do not want to have to work to make a direct match
      systemOut().clearHistory();//Clear the history so the next time we print
      //so we do not have to worry about the past output.
      bst.inOrderTraversal();
      assertFuzzyEquals("1 3 4 6 7 8 10 13 14", systemOut().getHistory());
   }
   
   public void testRemove() {
      Exception d = null;
      try{
         bst.delete(new Integer(1));//It possible to throw an exception if
         //deleting on an empty tree
      } catch(Exception e) {
         d = e;
         assertEquals(e.getMessage(), "cannot delete.");
         assertEquals(e.getClass(), RuntimeException.class);
         //There are a number of ways to test exceptions
         //One way would be to get the message that it prints, however, that
         //message will likely change in most exceptions (i.e. FileNotFound will
         //throw information about the file location). The other way to test an
         //exception would be to check the type of exception thrown. In addition,
         //it is wise to make sure an that the catch block is reached by setting
         //a marker value to guarantee it has been reached.
      }
      assertNotNull(d);//Make sure an exception was thrown
      String tree = "";
      for(int i=10; i < 20; i++) {
         bst.insert(new Integer(i));
         tree += i+" ";
      }
      for(int k=9; k > -1; k--) {
         bst.insert(new Integer(k));
         tree = k+" "+tree;
      }
      for(int j=0; j < 10; j++) {
         bst.delete(new Integer(j));//Test basic delete functionality
         tree = tree.replaceFirst(Integer.toString(j), "");
         systemOut().clearHistory();//Clear system so we only haver current tree
         bst.inOrderTraversal();//After each removal
         assertFuzzyEquals(tree, systemOut().getHistory());//See if the tree is what we expect
      }
      for(int l=19; l > 9; l--) {
         bst.delete(new Integer(l));
         tree = tree.replaceFirst(Integer.toString(l), "");
         systemOut().clearHistory();
         bst.inOrderTraversal();
         assertFuzzyEquals(tree, systemOut().getHistory());       
      }
      bst.insert(new Integer(10));//Handle the edge cases of deletion
      bst.insert(new Integer(8));//Deleting a  leaf
      bst.insert(new Integer(9));
      bst.insert(new Integer(6));
      bst.insert(new Integer(7));
      bst.delete(new Integer(6));
      systemOut().clearHistory();
      bst.inOrderTraversal();
      assertFuzzyEquals("7 8 9 10", systemOut().getHistory());
      bst.insert(new Integer(6));//Deleting an internal node
      bst.delete(new Integer(7));
      systemOut().clearHistory();
      bst.inOrderTraversal();
      assertFuzzyEquals("6 8 9 10", systemOut().getHistory());
      bst = new BST<Integer>();
      bst.insert(new Integer(10));//Deleting an internal node and pushing
      bst.insert(new Integer(8));//the new node up
      bst.insert(new Integer(9));
      bst.insert(new Integer(7));
      bst.delete(new Integer(8));
      systemOut().clearHistory();
      bst.inOrderTraversal();
      assertFuzzyEquals("7 9 10", systemOut().getHistory());
      bst = new BST<Integer>();
      bst.insert(new Integer(10));//Deleting an internal node and progressing
      bst.insert(new Integer(8));//down the left subtree to the rightmost
      bst.insert(new Integer(9));//node found in it.
      bst.insert(new Integer(6));
      bst.insert(new Integer(7));
      bst.delete(new Integer(8));
      systemOut().clearHistory();
      bst.inOrderTraversal();
      assertFuzzyEquals("6 7 9 10", systemOut().getHistory());
   }
}

上面的源代码展示了一种测试 BST 类的 add 和 delete 方法的可行方法。鉴于二叉搜索树是一种资料完备的数据 结构,有许多网站可供参考以获取测试所需的信息。 在本例中,我参考了 Wikipedia 上的条目,并选取了 可用的第一个二叉搜索树示例。 熟悉树遍历的人都知道, 对于一组给定的值,中序遍历将按从小到大的顺序 显示这些值,因此把所有值都添加到树中之后, 就很容易知道该预期什么。为了保证树被正确地 构建,我还选择测试前序遍历。测试 delete 函数要困难一些。为了获得正确的代码覆盖率, 必须考虑几种情况,即:删除叶结点、删除 只有一个子结点的内部结点、删除有两个子结点的内部结点,以及 删除一个不直接与叶结点相邻的内部结点。除了 所有这些之外,对 delete 函数的检查表明, 该函数有可能抛出 RuntimeException。为了测试这一点, 测试代码尝试强制 delete 函数抛出该异常,但是, 有可能该异常并不会被抛出,因而无法对 该异常执行实际的测试。因此,测试代码使用了一个哨兵值 来保证异常被抛出。否则测试将失败。 测试完异常之后,该函数接着尝试测试 delete 函数所涉及的各类情况。

在许多情况下,测试代码的等效性将满足测试所需的必要条件。然而,并非所有信息都可能严格等效,因为数据表示可能并不总是提供确切的信息。例如,在处理浮点数或双精度浮点数时,重要的是要测试结果,但为此进行严格等效测试并非易事。通过利用 assertEquals 方法的额外功能,可以为两个值之间的可接受差异阈值赋值,如下所示的代码。

public void testsquareroot(){
             assertEquals(Calculator.squareroot(2), 1.4142, .001);
             assertEquals(Calculator.squareroot(2), 1.4142, .000000000001);
     }

第一个测试会成功,但第二个测试会失败。为什么?嗯,在第二个 测试中阈值要精细得多,Java 会判定失败,因为 1.4142 与 1.4142135623730951 不够接近。在开发代码和选择数据类型时, 请记住这一点。

   «  9. JUnit 测试与你(JUnit Testing And You)   ::   目录   ::   11. JUnit 中的代码覆盖率(Code Coverage In JUnit)  »

关闭窗口