Java学习者论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

手机号码,快捷登录

恭喜Java学习者论坛(https://www.javaxxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,购买链接:点击进入购买VIP会员
JAVA高级面试进阶视频教程Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程

Go语言视频零基础入门到精通

Java架构师3期(课件+源码)

Java开发全终端实战租房项目视频教程

SpringBoot2.X入门到高级使用教程

大数据培训第六期全套视频教程

深度学习(CNN RNN GAN)算法原理

Java亿级流量电商系统视频教程

互联网架构师视频教程

年薪50万Spark2.0从入门到精通

年薪50万!人工智能学习路线教程

年薪50万!大数据从入门到精通学习路线年薪50万!机器学习入门到精通视频教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程 MySQL入门到精通教程
查看: 364|回复: 0

[Java框架学习]测试驱动的开发系列 第二部分:用JUnit测试Java类

[复制链接]
  • TA的每日心情
    开心
    2021-3-12 23:18
  • 签到天数: 2 天

    [LV.1]初来乍到

    发表于 2014-10-29 23:58:32 | 显示全部楼层 |阅读模式
    原文:http://www.theserverside.com/resources/article.jsp
         ?l=TestDrivenDevelopmentPart2
    Test_Driven Development Series Part I:
         Testing java Classes with JUnit
    By Wellie Chao  January 2004   一、简介(Introduction)        看到这儿你应该已经知道为什么测试如此重要了。如果还没有的话,请先阅读这个分为五个部分的系列论文中的第一篇。第一篇是为什么测试有益于企业级软件的概念性介绍。现在绝大多数的软件是分层实现的:表示层、逻辑层(业务逻辑就在这儿)和数据层。逻辑层是程序的重点,它包括程序所有的规则和行为。人们经常认为测试就是试用一个产品。对汽车来说这非常容易:打火、发动。对桌面应用也很容易:启动程序,四处点点鼠标、敲敲键盘,就可以测试你要测的功能。但是,你怎么测试一个由很多Java class组成的Jar文件呢?  
      
       
       
       

       
      
    二、测试Java类(Testing Java Classes) 当然,本质上说也可以算是启动Java类,然后点击按钮。测试一个Java类的途径就是在另一个Java类中调用这个类的方法。下面的例子是一个Java源文件。把程序列表1的内容存为FactorCalculator.java。 程序列表1 (FactorCalculator.java, taken from FactorCalculator.java.v1):

    import java.util.List;
    import java.util.ArrayList; public class FactorCalculator {
       public int[] factor(int number) {
         List factorList = new ArrayList();
         while(isDivisor(number, 2)) {
           factorList.add(new Integer(2));
           number /= 2;
         }
         int upperBound = (int)Math.sqrt(number) + 1;
         for(int i = 3; i <= upperBound; i += 2) {
           while(isDivisor(number, i)) {
             factorList.add(new Integer(i));
             number /= i;
           }
         }  if (number != 1) {
           factorList.add(new Integer(number));
           number = 1;
         }
         int[] intArray = new int[factorList.size()];
         for(int i = 0; i < factorList.size(); i++) {
           intArray = ((Integer)factorList.get(i)).intValue();
         }
         return intArray;
       }
       public boolean isPrime(int number) {
         boolean isPrime = true;
         int upperBound = (int)Math.sqrt(number) + 1;
         if (number == 2) {
           isPrime = true;
         } else if (isDivisor(number, 2)) {
           isPrime = false;
         } else {
           for(int i = 3; i <= upperBound; i += 2) {
             if (isDivisor(number, i)) {
               isPrime = false;
               break;
             }
           }
         }
         return isPrime;
       }
       public boolean isDivisor(int compositeNumber, int potentialDivisor) {
         return (compositeNumber % potentialDivisor == 0);
       }
    }  
    (省略180字,译者注) FactorCalculator类提供的接口很简单:  factor: 分解一个数获取素因子的方法。
      isPrime: 判断一个数是否素数的方法。
      isDivisor: 判断一个数是否能被另一个数整除的方法。
    这些public方法可以构成一个数学库的API。 要测试FactorCalculator,你可以创建一个有main方法可以从命令行调用的Java类。程序列表2就是这样一个测试类。 程序列表2 (CalculatorTest.java, taken from CalculatorTest.java.v1):
    public class CalculatorTest {
       public static void main(String [] argv) {
         FactorCalculator calc = new FactorCalculator();
         int[] intArray;
         intArray = calc.factor(100);
         if (!((intArray.length == 4) && (intArray[0] == 2) && (intArray[1] == 2) && (intArray[2] == 5) && (intArray[3] == 5))) {
           throw new RuntimeException("bad factorization of 100");
         }
         intArray = calc.factor(4);
         if (!((intArray.length == 2) && (intArray[0] == 2) && (intArray[1] == 2))) {
           throw new RuntimeException("bad factorization of 4");
         }
         intArray = calc.factor(3);
         if (!((intArray.length == 1) && (intArray[0] == 3))) {
           throw new RuntimeException("bad factorization of 3");
         }
         intArray = calc.factor(2);
         if (!((intArray.length == 1) && (intArray[0] == 2))) {
           throw new RuntimeException("bad factorization of 2");
         }
         boolean isPrime;
         isPrime = calc.isPrime(2);
         if (!isPrime) {
           throw new RuntimeException("bad isPrime value for 2");
         }
         isPrime = calc.isPrime(3);
         if (!isPrime) {
           throw new RuntimeException("bad isPrime value for 3");
         }
         isPrime = calc.isPrime(4);
         if (isPrime) {
           throw new RuntimeException("bad isPrime value for 4");
         }
         try {
           isPrime = calc.isPrime(1);
           throw new RuntimeException("isPrime should throw exception for numbers less than 2");
         } catch (IllegalArgumentException e) {
           // do nothing because throwing IAE is the proper action
         }
         boolean isDivisor;
         isDivisor = calc.isDivisor(6, 3);
         if (!isDivisor) {
           throw new RuntimeException("bad isDivisor value for (6, 3)");
         }
         isDivisor = calc.isDivisor(5, 2);
         if (isDivisor) {
           throw new RuntimeException("bad isDivisor value for (5, 2)");
         }
         try {
           isDivisor = calc.isDivisor(6, 0);
           throw new RuntimeException("isDivisor should throw exception when potentialDivisor (the second argument) is 0");
         } catch (ArithmeticException e) {
           // do nothing because throwing AE is the proper action
         }
         System.out.println("All tests passed.");
       }
    } 注意这两个try-catch块,一个测试isPrime,另一个测试isDivisor。有时候抛出异常才是某段代码的正确行为,测试这样的代码时,你必须捕获这个异常,看它是否你想要的。如果抛出的不是你想要的异常,你应该把它扔给(异常)处理链的下一级。如果这段代码应该有异常,但测试时没有抛出,你就要抛出你自己定义的异常,来通知程序功能错误。你应该使用与下文中要介绍的JUnit测试代码类似的模式,来测试那种本来就应该抛出一个或多个异常的部分代码。 为了节省篇幅,上面的测试代码省略了某些内容,例如当isDivisor方法的第二个参数是负数时会出现什么情况。 用javac *.java命令编译这个类,然后输入java CalculatorTest运行它。 你应该得到一个"不能判断小于2的数是否素数"的运行时异常。大概形式如下,当然显示的行数36可能因为你处理CalculatorTest.java中空格的方式而有所不同:
    Exception in thread "main" java.lang.RuntimeException: isPrime should throw exception for numbers less than 2
    at CalculatorTest.main(CalculatorTest.java:36) 换句话说,FactorCalculator的功能不正确。在isPrime方法最前面加一个判断就可以解决这个问题,判断参数小于2时就抛出IllegalArgumentException异常。就像下面的一小段代码:
    if (number < 2) {
       throw new IllegalArgumentException();
    } 把上面的代码放到FactorCalculator的isPrime方法的最前面。为了便于你参考,修改后的isPrime方法列在FactorCalculator.java.v2中,如果你打算直接使用这个文件,先把它改名为FactorCalculator.java。 增加了检查后,重新编译运行CalculatorTest,新的CalculatorTest应该可以通过所有测试。
    三、JUnit提供测试框架的优势(JUnit Provides Advantages as a Test Framework) 测试Java类的内部功能就是刚才你做的那些工作了。真正的测试和刚才的简单例子的主要区别是代码库的大小和复杂度。在处理一大堆代码时,你会需要收集情况报告。但上面的例子遇到第一个错误就停止了,它没有收集尽可能多的错误信息,也不能报告那些测试可以通过。如果一个测试不通过,就把整个测试重新编译、运行一遍,那开发过程肯定会非常慢。Bug经常是相互关联的,而且由各部分代码交互的地方引起。一次看到多个错误可以帮你分析和解决bug,对有关联的bug的处理也会加快。 在使用JUnit重写这个测试之前,你需要了解下述术语和测试概念:  1、单元测试(Unit test):单元测试是指一小段代码――绝大多数情况下都只有一个Java类――测试一个软件或者库非常有限的一个部分。单元测试检验的代码都很小,例如一个或几个类。通常是测试EJB组件和普通Java类库,不管这些类在服务器端(容器)环境中还是独立运行。与列表中提到的另一个概念功能测试比起来,单元测试的主要区别在于,单元测试的重点是最终用户一般看不到的内部组件,而功能测试的重点是"点击按钮"。在JUnit中,单元测试可能是TestCase类中的一个方法,也可能是整个TestCase类。从大小上讲一两页的代码对单元测试应该是合适的。如果单元测试达到十页就太夸张了,分成若干个粒度更细的测试会比较好。  2、功能测试(Functional test):功能测试就是站在最终用户的角度验证程序的功能是否正确。功能测试和黑盒测试是同一个意思。

      3、黑盒测试(Black box test):黑盒测试就是只根据对外发布的接口或公共约定,而忽略程序内部实现进行的测试。这通常意味着你只知道应该输入什么,只测试预期的输出,不知道程序如何生成这些输出,性能、边界影响等其它外部特征也不在你的考虑范围内,除非代码的这些方面特性是公共约定的一部分。如果你开发的软件库中有提供给其它开发者(你的最终用户)使用的API,黑盒测试就显得尤为重要。这个重要性不仅仅是指软件库能按公共接口说的做,软件库避免公共接口中禁止或省略的内容也很重要。如果你开发一个程序,遵守公共接口也是很重要的,因为它使你和同事之间能更有效的合作。

      4、白盒测试(White box test):白盒测试是在知道代码如何实现的情况下测试一段代码的功能。当你要测试公共约定中没有指定,但很重要的行为,例如性能问题时,白盒测试就派上用场了。在测试某些特别复杂的算法或业务逻辑时,也需要白盒测试。这种情况下,通过白盒测试你可以把注意力集中在可能出现错误的地方,这在黑盒测试中由于缺乏对内部情况的了解很难做到。

      5、容器内测试(In-container test):容器内测试在Servlet或EJB容器内部进行,因此能更直接的和要测试的代码通信。Jakarta Cactus项目实现了一个免费的测试工具Cactus,它让你和要测试的代码在同一个容器内执行,不管这个容器是servlet容器还是EJB容器。容器内测试对黑盒功能测试没什么用,它的作用体现在单元测试上。

      6、测试用例(Test case):Test case在JUnit中就是一组相关的测试。Test case表现为继承junit.framework.TestCase的类。Test case通常有多个方法,每个方法测试程序一方面的行为。Test case中的测试方法习惯用test作前缀命名,但并不是必须这样做,只要不与其它方法产生冲突就可以。

      7、测试集(test suite):Test suite是一组test case或test suites。它表现为继承junit.framework.TestSuite的类。没有任何限制要求test suite只包括test case或只包括test suite,它可以既有test case,又有test suite。一个test suite的子test suite也可以包括test case和test suite,因此允许嵌套测试。只要你愿意,你可以建立几组test case,每一组测试程序的一个明确的小方面。一组可以形成一个test suite。然后你可以把它们综合到一个主test suite中,最后用这个主test suite测试整个程序,一个功能点一个功能点的测试。

      8、测试启动器(Test runner):Test runner是启动测试过程的JUnit类。你调用test runner,它依次执行你预订的测试。有几种办法可以定义要test runner执行的测试。这些办法在下面的第五部分"指定要运行的测试"中介绍。JUnit有三种不同的test runners:text、AWT和Swing,类名分别是junit.textui.TestRunner、junit.awtui.TestRunner和junit.swingui.TestRunner。
    四、编写JUnit测试(Writing a Test with JUnit) 编写JUnit测试,只要扩展junit.framework.TestCase类就可以了。你的TestCase子类将按你希望的顺序调用test cases,包括可能的测试前设置和测试后清除。设置在setUp方法中进行。清除在tearDown方法中进行。你可以,但不是必须,重载这两个方法做你想做的事。 下面是对上面的例子用JUnit进行重写的test case: 程序列表3 (CalculatorTest.java, taken from CalculatorTest.java.v2): import junit.framework.TestCase; public class CalculatorTest extends TestCase {   private FactorCalculator calc;   public CalculatorTest(String name) {
         super(name);
       }   protected void setUp() {
         calc = new FactorCalculator();
       }   public void testFactor() {
         int numToFactor;
         int[] factorArray;
         int[] correctFactorArray;     numToFactor = 100;
         factorArray = calc.factor(numToFactor);
         correctFactorArray = new int[] {2, 2, 5, 5};
         assertTrue("bad factorization of " + numToFactor, isSameFactorArray(factorArray, correctFactorArray));     numToFactor = 4;
         factorArray = calc.factor(numToFactor);
         correctFactorArray = new int[] {2, 2};
         assertTrue("bad factorization of " + numToFactor, isSameFactorArray(factorArray, correctFactorArray));     numToFactor = 3;
         factorArray = calc.factor(numToFactor);
         correctFactorArray = new int[] {3};
         assertTrue("bad factorization of " + numToFactor, isSameFactorArray(factorArray, correctFactorArray));     numToFactor = 2;
         factorArray = calc.factor(numToFactor);
         correctFactorArray = new int[] {2};
         assertTrue("bad factorization of " + numToFactor, isSameFactorArray(factorArray, correctFactorArray));
       }   // presumes both factor arrays are in numeric order
       private boolean isSameFactorArray(int[] factorArray1, int[] factorArray2) {
         boolean isSame = false;
         if (factorArray1.length == factorArray2.length) {
           isSame = true;
           for(int i = 0; i < factorArray1.length; i++) {
             if (factorArray1 != factorArray2) {
               isSame = false;
               break;
             }
           }
         }
         return isSame;
       }   public void testIsPrime() {
         int numToCheck;
         boolean isPrime;     numToCheck = 2;
         isPrime = calc.isPrime(numToCheck);
         assertTrue("bad isPrime value for " + numToCheck, isPrime);     numToCheck = 3;
         isPrime = calc.isPrime(numToCheck);
         assertTrue("bad isPrime value for " + numToCheck, isPrime);     numToCheck = 4;
         isPrime = calc.isPrime(numToCheck);
         assertFalse("bad isPrime value for " + numToCheck, isPrime);     try {
           numToCheck = 1;
           isPrime = calc.isPrime(numToCheck);
           fail("isPrime should throw exception for numbers less than 2");
         } catch (IllegalArgumentException e) {
           // do nothing because throwing IAE is the proper action
         }
       }   public void testIsDivisor() {
         int numToCheck;
         int potentialDivisor;
         boolean isDivisor;     numToCheck = 6;
         potentialDivisor = 3;
         isDivisor = calc.isDivisor(numToCheck, potentialDivisor);
         assertTrue("bad isDivisor value for (" + numToCheck + ", " + potentialDivisor + ")", isDivisor);     numToCheck = 5;
         potentialDivisor = 2;
         isDivisor = calc.isDivisor(numToCheck, potentialDivisor);
         assertFalse("bad isDivisor value for (" + numToCheck + ", " + potentialDivisor + ")", isDivisor);     try {
           numToCheck = 6;
           potentialDivisor = 0;
           isDivisor = calc.isDivisor(numToCheck, potentialDivisor);
           fail("isDivisor should throw an exception when potentialDivisor is 0 but did not");
         } catch (ArithmeticException e) {
           // do nothing because throwing AE is the proper action
         }
       }
    }
    通过方法名assertXxx你可以让JUnit知道你想要的结果,其中Xxx是True、Fase、Equals或者其它条件。JUnit记录assertXxx方法的通过/失败状态,并在执行完所有测试后反馈给你。这儿是一些JUnit中有签名和操作描述的断言(assert)方法: assertTrue(String errorMessage, boolean booleanExpression):
    检查booleanExpression值是否为true。如果不是,把errorMessage添加到错误报告的显示列表中。

    assertFalse(String errorMessage, boolean booleanExpression):
    检查booleanExpression值是否为false。如果不是,把errorMessage添加到错误报告的显示列表中。

    assertEquals(String errorMessage, Object a, Object b):
    检查对象a是否等于对象b,通过equals方法,如果不是,把errorMessage添加到错误报告的显示列表中。对象a是期望值,对象b是要测试的程序实际返回的值。

    assertNull(String errorMessage, Object o):
    检查对象o是否为null。如果不是,把errorMessage添加到错误报告的显示列表中。 要查看所有断言方法的完整列表,请参考Assert类的javadoc文档(http://www.junit.org/junit/javadoc/index.htm)。 你可以在整个测试代码中随意使用assertXxx语句,来确认你要测的代码中某个条件结果为true(或者false,视情况而定)。   五、指定要运行的测试(Specifying Which Tests to Run) 要运行你的测试,你需要:
    一个TestRunner类的实例。
    一个测试类(例如本例的MyTestClass类)的实例,它包含你要运行的测试的。这个类必须继承junit.framework.TestCase。
    告诉这个TestRunner实例你的MyTestClass实例中哪些测试要运行的途径。 创建TestRunner的实例和指定MyTestClass实例非常容易,你可以通过下面的命令:
    java junit.textui.TestRunner MyTestClass 对别的UI可以用其相应的TestRunner代替junit.textui.TestRunner,例如AWT的junit.awtui.TestRunner和Swing的junit.swingui.TestRunner。你还要用你自己的测试类的名字替换MyTestClass。 有两种途径可以让TestRunner知道你要运行MyTestClass类中的哪些测试。一个是显式途径,一个是默认途径。在MyTestClass中,你可以选择是否包含一个public static方法suite,这个方法没有任何参数,返回Test对象。更准确地说,它返回一个实现Test接口的对象,因为Test是接口,不是类。大多数时候你都使用TestSuite和你自己的TestCase子类,TestSuite和TestCase都实现了Test接口。 如果你在MyTestClass方法中省略了suite方法,那么TestRunner通过reflection机制找到MyTestClass类中所有以"test"为前缀命名的方法,并运行它们。这是通知TestRunner要运行哪些测试的默认途径。 如果你在MyTestClass中实现了suite方法,TestRunner调用suite方法,通过suite方法返回的Test对象,TestRunner获悉它要进行的测试。这是显式途径。TestCase和TestSuite类都实现Test接口,意味着你可以只返回一个TestCase,也可以返回一个包含0到多个TestCase/TestSuite的TestSuite,这样就可以进行多个测试和层次测试。
    在junit.framework.TestCase中指定要运行的测试 在TestCase中有两种方式可以指定测试方法:一个静态一个动态。静态方法是重TestCase的runTest方法,在其中调用你的测试。例如: import junit.framework.TestCase;
    public class MySimpleTest extends TestCase {
       public MySimpleTest(String name) {
         super(name);
       }
       public void runTest() {
         testTurnLeft();
       }
       public void testTurnLeft() {
         ... code here ...
       }
    } 有时最简单最灵活的重载TestCase.runTest的方式是用一个匿名内部类。下面的代码描述了这种方式:
    TestCase testCase = new MySimpleTest("myNameForTurnLeft") {
       public void runTest() {
         testTurnLeft();
       }
    } 匿名内部类让你在实例化test类的类中重载runTest,这样在不同的地方可以有不同的runTest实现,它们都使用MySimpleTest作为实际的测试方法。如果你在test类的suite方法中初始化它自己,这个初始化test类的类就是它自己。 通过构造器的name参数可以在TestCase中动态指定测试方法。对上面的MySimpleTest类,你可以写成:
    TestCase testCase = new MySimpleTest("testTurnLeft"); 因为你没有重载runTest,TestCase类的默认实现将通过reflection找到方法testTurnLeft。你可以用任何你喜欢的名字代替"testTurnLeft"。
    六、用junit.framework.TestSuite指定多层测试(Specifying a Hierarchy of Tests to Run With junit.framework.TestSuite) TestSuite类可以把多个测试打包成一组。基本形式如下:
    TestSuite testSuite = new TestSuite();
    testSuite.addTest(new MySimpleTest("testTurnLeft"));
    testSuite.addTest(new CalculatorTest("testIsDivisor"));
    testSuite.addTest(new TestSuite(MyThirdTest.class)); 前两个addTest方法是直接调用。TestSuite.addTest方法接受实现Test接口的对象作参数。MySimpleTest和CalculatorTest类都是TestCase的子类,而TestCase实现Test接口。通过前两个addTest方法,你只是把两个测试方法添加到TestSuite实例要执行的测试列表中。 第三个addTest调用描述如何通过在TestSuite实例中包含TestSuite实例来创建层次测试。TestSuite类实现Test接口,所以可以作为addTest方法的参数。第三个addTest调用中,新的TestSuite对象包含MyThirdTest类所有的testXxx方法。没有任何限制要求addTest方法中指定的TestSuite实例是单层列表,子TestSuite还可以包含子TestSuite。
    七、再论TestSuite.suite()方法(Back to the TestCase.suite() Method) 现在我们对如何指定TestCase和TestSuite要运行的测试,已经很清楚了,让我们再回过头来看看TestRunner需要的TestCase.suite()方法。这儿有一个TestCase.suite()方法的例子,它添加一个TestCase类的一个测试方法,另一个TestCase类的所有测试方法,以及一个子TestSuite所有层次的测试方法。 程序列表4 (a suite method demonstrating many different ways of specifying tests): public static suite() {
       TestSuite globalTestSuite = new TestSuite();   TestCase addToCartTestCase = new ShopCartTest("testAddToCart");
       globalTestSuite.addTest(addToCartTestCase);   TestCase checkOutTestCase = new ShopCartTest("testCheckOut");
       globalTestSuite.addTest(checkOutTestCase);   TestSuite calcTestSuite = new TestSuite(CalculatorTest.class);
       globalTestSuite.addTest(calcTestSuite);   TestSuite fileModuleTestSuite = new TestSuite();
       fileModuleTestSuite.addTest(new ImportExportTest("testImport"));
       fileModuleTestSuite.addTest(new TestSuite(SaveFileTest.class));
       globalTestSuite.addTest(fileModuleTestSuite);   return globalTestSuite;
    } 好,你已经了解了如何向TestRunner指定测试的不同方法,你应该开始这些测试了。如果你在CalculatorTest中添加了一个suite方法,把它删掉,因为在下一小节中TestRunner将运行CalculatorTest类中所有的testXxx方法。suite方法在你要做大量测试时非常重要。 八、运行测试(Running the Test) 输入javac -classpath ~/packages/junit3.8.1/junit.jar *.java编译CalculatorTest类。用你机器上junit.jar文件的路径代替"~/packages/junit3.8.1/junit.jar"。输入java -classpath ~/packages/junit3.8.1/junit.jar:. junit.textui.TestRunner CalculatorTest运行测试。这儿的junit.jar路径也需要替换。为了避免每次都要在命令行指定classpath,把JUnit库和当前目录都加到classpath中。Linux下你可以在bash shell中用这两个命令:
    CLASSPATH=~/packages/junit3.8.1/junit.jar:.
    export CLASSPATH 注意把"~/packages/junit3.8.1/junit.jar"替换为junit.jar文件的正确路径,而且不要忘了后面的冒号和点。Windows下设置环境变量的命令是"set",你可以用它把CLASSPATH设置为类似的值,除了正斜杠改成反斜杠。把"."加入classpath是为了让JUnit TestRunner能找到当前目录下的CalculatorTest。对本文来说,你应该使用"."而不是当前路径的硬编码,因为你还要练习其它的例子,这样无论你在做那个例子,你都访问和执行新的当前目录下的类。下面假定你已经正确设置了你的classpath。 运行CalculatorTest中的测试后,你应该看到下面的输出:
    ...
    Time: 0.008 OK (3 tests) 一串点表示JUnit正在运行,JUnit还在统计行显示通过或失败的测试数目。如果某个测试失败了,显示结果可能就不是上面那样,而是: ..F
    Time: 0.01
    There was 1 failure:
    1) testAddition(Test) "expected:<5> but was:<4>" FAILURES!!!
    Tests run: 2,  Failures: 1,  Errors: 0
    九、其它TestRunner类和执行方法(Different TestRunner Classes and Ways of Executing Them)
      有好几个TestRunner你可以使用:text、AWT和Swing。对应的类分别是junit.textui.TestRunner、junit.awtui.TestRunner和junit.swingui.TestRunner。运行它们的命令类似:
    java junit.awtui.TestRunner CalculatorTest --或者-- java junit.swingui.TestRunner CalculatorTest AWT和Swing版本的TestRunner需要在Windows、OS X或X11等图形环境中使用。它们用交互的图形格式显示运行结果。text UI是最常用的,因为测试一般都用批处理模式运行,这时交互是一种缺点。 当你调用TestRunner,把测试类的名字传给它时,TestRunner加载你的类,使用reflection找到所有以"test"开始的方法。如果你不想在命令行用java调用TestRunner类,你还有另一重办法:直接调用包含test suite的类的main方法。 输入下述内容作为TestCase子类的main方法:
    public static void main(String[] argv) {
       junit.textui.TestRunner.run(suite());
    } 如果你需要的话,用junit.awtui.TestRunner或junit.swingui.TestRunner代替junit.textui.TestRunner。 方便起见,示例文件中提供了CalculatorTest类的另一个版本CalculatorTest.java.v3,它包含suite方法和前面描述的main方法,当然,你在使用之前需要把它改名为CalculatorTest.java。如果你使用的环境是Linux,或者是Cygwin在Windows下模拟的UNIX,你可以用diff命令查看不同版本之间的差别,这在学习新东西时通常很有用。例如,输入diff CalculatorTest.java.v2 CalculatorTest.java.v3就可以查看CalculatorTest.java.v2和CalculatorTest.java.v3之间的区别。 编译新的CalculatorTest类之后,就可以运行了。这次不必输入java junit.textui.TestRunner CalculatorTest,可以用java CalculatorTest代替。 十、在程序和测试中添加功能(Adding Functionality to Your Application and to Your Test)
      现在假如说你要在FactorCalculator中添加功能。测试驱动的开发方式建议你首先增加测试,验证测试失败,(代码还没写当然失败,译者注。)然后再编写新功能的代码,并确保测试通过。现在假如你要增加一个求最大公约数的方法,名字叫"gcd"。(此处省略20字,译者注)可以用下面三条验证:6和4的最大公约数是2。36和18的最大公约数是18。30和75的最大公约数是15。 刚才那些都是正常的例子。除此之外,你还应该测试边缘和错误的情况,例如gcd(2, 1)和gcd(3, -1)。下面的代码就可以做这些测试: 程序列表 5: public void testGcd() {
       assertEquals("bad value for gcd(6, 4)", 2, calc.gcd(6, 4));
       assertEquals("bad value for gcd(36, 18)", 18, calc.gcd(36, 18));
       assertEquals("bad value for gcd(30, 75)", 15, calc.gcd(30, 75));
       assertEquals("bad value for gcd(2, 1)", 1, calc.gcd(2, 1));
       try {
         calc.gcd(3, -1);
         fail("gcd should throw exception for when either argument is less than 1");
       } catch (IllegalArgumentException e) {
         // do nothing because throwing IAE is the proper action
       }
    } 把程序列表5中的代码添加到CalculatorTest.java。如果你不想敲键盘,你可以从示例代码中拷贝CalculatorTest.java.v4,把名字改成CalculatorTest.java。 为了让CalculatorTest能编译,需要在FactorCalculator中添加一个stub,你在CalculatorTest中调用了gcd方法,你就必须在FactorCalculator中定义gcd方法。把下述内容加到FactorCalculator中: public int gcd(int a, int b) {
       return 1;
    } 如果你不想敲键盘,你可以从示例代码中拷贝FactorCalculator.java.v3,把名字改成FactorCalculator.java。 很明显,大部分情况下上面的gcd方法都返回错误结果,但测试驱动的开发方式信奉"先让它出错,然后再纠正它"( "errors first, then correction of errors")为最有效的开发模式。测试代码和其它代码一样,也可能出错。有可能你的测试代码不能发现程序中的错误。如果你在开发程序功能前编写测试代码,你就可以确保测试正确发现错误,因此减少错误被疏漏的机会。 输入javac *.java编译FactorCalculator和CalculatorTest类。在编译和运行时都需要确保JUnit库在classpath中。输入java CalculatorTest。你将看到下面的输出: ....F
    Time: 0.01
    There was 1 failure:
    1) testGcd(CalculatorTest)junit.framework.AssertionFailedError: bad value for gcd(6, 4) expected:<2> but was:<1>
             at CalculatorTest.testGcd(CalculatorTest.java:125)
             ...
             at CalculatorTest.main(CalculatorTest.java:14) FAILURES!!!
    Tests run: 4,  Failures: 1,  Errors: 0 开始测试失败了,这是意料之中的结果,也正是我们想要的。如果它不失败,那就意味着测试的设计或实现出了问题。JUnit会把你给它的信息显示出来,所以应该写有意义的错误信息。现在修复FactorCalculator类,让它通过测试。删除FactorCalculator类gcd方法中的"return 1;"一行,用下面的代码代替: 程序列表6 (functional implementation of gcd method): int gcd = 1;
    int smallerInt = (a < b) ? a : b;
    for(int i = smallerInt; i > 1; i--) {
       if (isDivisor(a, i) && isDivisor(b, i)) {
         gcd = i;
         break;
       }
    }
    return gcd;
    如果你不想敲键盘,你可以从示例代码中拷贝FactorCalculator.java.v4,把名字改成FactorCalculator.java。 输入javac FactorCalculator.java重新编译javac FactorCalculator。输入java CalculatorTest重跑测试。你仍然得到错误,因为参数错误时gcd方法没有抛出异常。调用gcd(3, -1)应该产生一个IllegalArgumentException,但事实上没有。把下面的代码加到gcd方法的最前面可以解决这个问题。 if ((a < 1) || (b < 1)) {
       throw new IllegalArgumentException();
    } 修改后的FactorCalculator是示例代码中的FactorCalculator.java.v5,你可以更名为FactorCalculator.java。重新编译FactorCalculator.java后运行测试。一切正常,测试通过,状态报告类似: ....
    Time: 0.008 OK (4 tests)
    十一、总结(Conclusion)
      现在你已经知道如何用JUnit进行单元测试了,在你自己的代码中进行试验吧,亲身体会一下程序测试的好处。 现在准备进入测试驱动开发系列的下一章。下一章,也就是五个部分中的第三部分,将带你进入如何在EJB容器中测试服务器端EJB组件的操作细节。
    作者
    Wellie Chao从1984年开始对软件开发产生兴趣,并在1994年成为职业程序员至今。他领导了几个构建企业应用的软件项目,在Java和Perl语言上有很深的造诣,他出版过几本Java的书籍,其中"Core Java Tools"(Prentice Hall)讲述了用Ant、CVS和JUnit等开源Java工具进行极限编程和测试驱动的开发等主题。他在IBMdeveloperWorks、DevX、TheServerSide和其它地方发表的论文涵盖了开发企业软件的Java程序员感兴趣的主题。
    他荣幸的毕业于哈佛大学,在那儿他攻读经济学和计算机,他现在住在纽约。  

      
      
       
       

         
       

         
       
      



    源码下载:http://file.javaxxz.com/2014/10/29/235832468.zip
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|Java学习者论坛 ( 声明:本站资料整理自互联网,用于Java学习者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-5-5 11:18 , Processed in 0.335304 second(s), 36 queries .

    Powered by Discuz! X3.4

    © 2001-2017 Comsenz Inc.

    快速回复 返回顶部 返回列表