# 测试驱动的开发系列 第二部分：用JUnit测试Java类

Test_Driven Development Series Part I:Testing Java Classes with JUnit
By Wellie Chao

January 2004

import java.util.List;
import java.util.ArrayList;

public class FactorCalculator {
public int[] factor(int number) {
List factorList = new ArrayList();
while(isDivisor(number, 2)) {
number /= 2;
}
int upperBound = (int)Math.sqrt(number) + 1;
for(int i = 3; i <= upperBound; i += 2) {
while(isDivisor(number, i)) {
number /= i;
}
}
if (number != 1) {
number = 1;
}
int[] intArray = new int[factorList.size()];
for(int i = 0; i < factorList.size(); i++) {
intArray[i] = ((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 class CalculatorTest {
public static void main(String [] argv) {
FactorCalculator calc = new FactorCalculator();
int[] intArray;
intArray = calc.factor(100);
if (!((intArray.length == 4) && (intArray == 2) && (intArray == 2) && (intArray == 5) && (intArray == 5))) {
throw new RuntimeException("bad factorization of 100");
}
intArray = calc.factor(4);
if (!((intArray.length == 2) && (intArray == 2) && (intArray == 2))) {
throw new RuntimeException("bad factorization of 4");
}
intArray = calc.factor(3);
if (!((intArray.length == 1) && (intArray == 3))) {
throw new RuntimeException("bad factorization of 3");
}
intArray = calc.factor(2);
if (!((intArray.length == 1) && (intArray == 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.");
}
}

Exception in thread "main" java.lang.RuntimeException: isPrime should throw exception for numbers less than 2
at CalculatorTest.main(CalculatorTest.java:36)

if (number < 2) {
throw new IllegalArgumentException();
}

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。

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[i] != factorArray2[i]) {
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
}
}
}

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添加到错误报告的显示列表中。

java junit.textui.TestRunner MyTestClass

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 testCase = new MySimpleTest("myNameForTurnLeft") {
public void runTest() {
testTurnLeft();
}
}

TestCase testCase = new MySimpleTest("testTurnLeft");

TestSuite类可以把多个测试打包成一组。基本形式如下：
TestSuite testSuite = new TestSuite();

public static suite() {
TestSuite globalTestSuite = new TestSuite();

TestCase checkOutTestCase = new ShopCartTest("testCheckOut");

TestSuite calcTestSuite = new TestSuite(CalculatorTest.class);

TestSuite fileModuleTestSuite = new TestSuite();

return globalTestSuite;
}

CLASSPATH=~/packages/junit3.8.1/junit.jar:.
export CLASSPATH

...
Time: 0.008

OK (3 tests)

..F
Time: 0.01
There was 1 failure:

FAILURES!!!
Tests run: 2,  Failures: 1,  Errors: 0

java junit.awtui.TestRunner CalculatorTest

--或者--

java junit.swingui.TestRunner CalculatorTest

AWT和Swing版本的TestRunner需要在Windows、OS X或X11等图形环境中使用。它们用交互的图形格式显示运行结果。text UI是最常用的，因为测试一般都用批处理模式运行，这时交互是一种缺点。

public static void main(String[] argv) {
junit.textui.TestRunner.run(suite());
}

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
}
}

public int gcd(int a, int b) {
return 1;
}

....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

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;

if ((a < 1) || (b < 1)) {
throw new IllegalArgumentException();
}

....
Time: 0.008

OK (4 tests)

Wellie Chao从1984年开始对软件开发产生兴趣，并在1994年成为职业程序员至今。他领导了几个构建企业应用的软件项目，在Java和Perl语言上有很深的造诣，他出版过几本Java的书籍，其中“Core Java Tools”（Prentice Hall）讲述了用Ant、CVS和JUnit等开源Java工具进行极限编程和测试驱动的开发等主题。他在IBMdeveloperWorks、DevX、TheServerSide和其它地方发表的论文涵盖了开发企业软件的Java程序员感兴趣的主题。
{"ft_lang_label":"__label__zh","ft_lang_prob":0.563955,"math_prob":0.9339123,"size":19169,"snap":"2023-14-2023-23","text_gpt3_token_len":8879,"char_repetition_ratio":0.17401513,"word_repetition_ratio":0.179941,"special_character_ratio":0.19421983,"punctuation_ratio":0.20821236,"nsfw_num_words":0,"has_unicode_error":false,"math_prob_llama3":0.9531013,"pos_list":[0],"im_url_duplicate_count":[null],"WARC_HEADER":"{\"WARC-Type\":\"response\",\"WARC-Date\":\"2023-06-02T16:03:10Z\",\"WARC-Record-ID\":\"<urn:uuid:287c7d07-6009-4f74-803d-8ad0787f2df4>\",\"Content-Length\":\"36215\",\"Content-Type\":\"application/http; msgtype=response\",\"WARC-Warcinfo-ID\":\"<urn:uuid:749f9015-d833-42b3-af18-fedb40ae8dec>\",\"WARC-Concurrent-To\":\"<urn:uuid:9d1a5127-8c79-45c2-9895-d2f89fbe9f32>\",\"WARC-IP-Address\":\"104.21.29.208\",\"WARC-Target-URI\":\"https://com.8s8s.com/it/it16775.htm\",\"WARC-Payload-Digest\":\"sha1:YZTZPNWGGSM25WI4ET4IIB7MZGGYSZBR\",\"WARC-Block-Digest\":\"sha1:H7APFB3JGUYRXKGOO62WCNSQCPYLROFY\",\"WARC-Identified-Payload-Type\":\"application/xhtml+xml\",\"warc_filename\":\"/cc_download/warc_2023/CC-MAIN-2023-23/CC-MAIN-2023-23_segments_1685224648695.4_warc_CC-MAIN-20230602140602-20230602170602-00221.warc.gz\"}"}