The test code must be written to do a few things:
一、测试流程:
1) Setup all conditions needed for testing (create any re-quired objects, allocate any needed resources, etc.)
初始化资源
2) Call the method to be tested
调用方法开始测试
3) Verify that the method to be tested functioned as ex-Pected
验证被测试方法是否按期望方式运行
4) Clean up after itself
完成测试后自行清理资源
下面让以上述为依据看看testCase的源代码:
public abstract class TestCase extends Assert implements Test {
/**
* the name of the test case测试用例名称
*/
private String fName;
/**
* No-arg constructor to enable serialization. This method 构造函数
* is not intended to be used by mere mortals without calling setName().
*/
public TestCase() {
fName= null;
}
/**
* Constructs a test case with the given name.构造函数
*/
public TestCase(String name) {
fName= name;
}
/**
* Counts the number of test cases executed by run(TestResult result).记录测试用例数目
*/
public int countTestCases() {
return 1;
}
/**
* Creates a default TestResult object//创建默认TestResult对象,里面存储测试结果
*
* @see TestResult
*/
protected TestResult createResult() {
return new TestResult();
}
/**
* A convenience method to run this test, collecting the results with a
* default TestResult object.执行测试很简易的方法,使用默认的TestResult
*
* @see TestResult
*/
public TestResult run() {
TestResult result= createResult();
run(result);
return result;
}
/**
* Runs the test case and collects the results in TestResult.执行测试并把手机测试结果信息存入指定TestResult
*/
public void run(TestResult result) {
result.run(this);
}
/**
* Runs the bare test sequence. 执行测试方法,包括初始化和销毁方法
* @throws Throwable if any exception is thrown
*/
public void runBare() throws Throwable {
Throwable exception= null;
setUp();
try {
runTest();
} catch (Throwable running) {
exception= running;
}
finally {
try {
tearDown();
} catch (Throwable tearingDown) {
if (exception == null) exception= tearingDown;
}
}
if (exception != null) throw exception;
}
/**
* Override to run the test and assert its state. 执行测试方法
* @throws Throwable if any exception is thrown
*/
protected void runTest() throws Throwable {
assertNotNull("TestCase.fName cannot be null", fName); // Some VMs crash when calling getMethod(null,null);
Method runMethod= null;
try {
// use getMethod to get all public inherited
// methods. getDeclaredMethods returns all
// methods of this class but excludes the
// inherited ones.
runMethod= getClass().getMethod(fName, (Class[])null);
} catch (NoSuchMethodException e) {
fail("Method \""+fName+"\" not found");
}
if (!Modifier.isPublic(runMethod.getModifiers())) {
fail("Method \""+fName+"\" should be public");
}
try {
runMethod.invoke(this);
}
catch (InvocationTargetException e) {
e.fillInStackTrace();
throw e.getTargetException();
}
catch (IllegalAccessException e) {
e.fillInStackTrace();
throw e;
}
}
/**
* Sets up the fixture, for example, open a network connection.
* This method is called before a test is executed. 测试前的初始化方法
*/
protected void setUp() throws Exception {
}
/**
* Tears down the fixture, for example, close a network connection.
* This method is called after a test is executed. 测试后的销毁方法
*/
protected void tearDown() throws Exception {
}
/**
* Returns a string representation of the test case
*/
@Override
public String toString() {
return getName() + "(" + getClass().getName() + ")";
}
/**
* Gets the name of a TestCase返回测试案例的名称
* @return the name of the TestCase
*/
public String getName() {
return fName;
}
/**
* Sets the name of a TestCase设定测试案例的名称
* @param name the name to set
*/
public void setName(String name) {
fName= name;
}
}
二、运行方式:
您定义自己的TestCase,并使用TestRunner来运行测试,事实上TestRunner并不直接运行 TestCase上的单元方法,而是透过TestSuite,TestSuite可以将数个TestCase在一起,而让每个TestCase保持简单。
public class SimpleTest extends TestCase {
protected int fValue1;
protected int fValue2;
SimpleTest(String name){
super(name);
}
@Override
protected void setUp() {
fValue1= 2;
fValue2= 3;
}
public void testAdd() {
double result= fValue1 + fValue2;
// forced failure result == 5
assertTrue(result == 6);
}
public void testDivideByZero() {
int zero= 0;
int result= 8/zero;
result++; // avoid warning for not using result
}
public void testEquals() {
assertEquals(12, 12);
assertEquals(12L, 12L);
assertEquals(new Long(12), new Long(12));
assertEquals("Size", 12, 13);
assertEquals("Capacity", 12.0, 11.99, 0.0);
}
public static void main (String[] args) {
junit.textui.TestRunner.run(suite());
}
}
在这个例子中,您并没有看到任何的TestSuite,事实上,如果您没有提供任何的TestSuite,TestRunner会自己建立一个,然后这个 TestSuite会使用反射(reflection)自动找出testXXX()方法。
如果您要自行生成TestSuite,则在继承TestCase之后,提供静态的(static)的suite()方法,例如:
public static Test suite() {
/*
* the type safe way
**/
TestSuite suite= new TestSuite();
suite.addTest(
new SimpleTest("add") {
@Override
protected void runTest() { testAdd(); }
}
);
suite.addTest(
new SimpleTest("testDivideByZero") {
@Override
protected void runTest() { testDivideByZero(); }
}
);
return suite;
/*
* the dynamic way
*/
//return new TestSuite(SimpleTest.class);
}
JUnit并没有规定您一定要使用testXXX()这样的方式来命名您的测试方法,如果您要提供自己的方法(当然JUnit 鼓励您使用testXXX()这样的方法名称),则可以如上撰写,为了要能够使用建构函式提供测试方法名称,您的TestCase必须提供如下的建构函式:
public SimpleTest (String testMethod) {
super(testMethod);
}
如果要加入更多的测试方法,使用addTest()就可以了,suite()方法传回一个TestSuite物件,它与 TestCase都实作了Test介面,TestRunner会调用TestSuite上的run()方法,然后TestSuite会将之委託给 TestCase上的run()方法,并执行每一个testXXX()方法。
除了组合TestCase之外,您还可以将数个TestSuite组合在一起,例如:
public static Test suite() {
TestSuite suite= new TestSuite();
suite.addTestSuite(TestCase1.class);
suite.addTestSuite(TestCase2.class);
return suite;
}
如此之来,您可以一次运行所有的测试,而不必个别的运行每一个测试案例,您可以写一个运行全部测试的主测试,而在使用TestRunner时呼叫 suite()方法,例如:
junit.textui.TestRunner.run(TestAll.suite());
TestCase与TestSuite都实作了Test介面,其运行方式为 Command 模式 的一个实例,而TestSuite可以组合数个TestSuite或TestCase,这是 Composite 模式的一个实例。
三、失败和错误的区别
通常情况下是不希望自己编写的Java代码抛出错误的,而只应该抛出异常。通常来说,应该让Java虚拟机本身来抛出错误。因为一个错误意味着低级别的、不可恢复的问题,例如:无法装载一个类,这种问题我们也不期望被恢复。出于这个原因,也许我们对JUnit通过抛出错误来表示一个断言的失败而感到有些奇怪。
JUnit抛出的是错误而不是异常,这是因为在Java中,错误是不需要检查的;因此,不是每个方法都必须声明它会抛出哪些错误。你可能会认为RuntimeException可以完成相同的功能,但是如果JUnit抛出这种在产品代码中可能抛出的异常的类型,JUnit测试就会和你的产品相互影响。这种影响会降低JUnit的价值。
当你的测试包含了一个失败的断言,JUnit会将它算做一个失败的测试;但是如果你的测试抛出了一个异常(并且没有捕获它),JUnit将它看成是一个错误。这个区别是很细微的,却是非常有用的:一个失败的断言通常表示产品代码中有问题,而一个错误却表示测试本身或周围的环境存在着问题。也许你的测试期望了一个不该期望的错误的异常对象,或者错误地在一个null的应用上调用了一个函数。也有可能磁盘已经满了,或者网络连接不可用,或者是一个文件找不到了。JUnit并不把这个算做产品代码的缺陷,因此它只是在表达,“有些不对劲了。我不能分辨这个测试是否通过。请解决这个问题并再重新测试一次。”这就是失败和错误的区别。
JUnit的测试运行器会报告一个测试运行的结果,格式如下:“78 run, 2 failures,1 error。”从这个结果可以判断有75个测试已经通过,有两个失败,还有一个没有结论。我们的建议是先去调查那个错误,解决了此问题以后再重新运行这个测试。也许在解决了这个错误以后,所有的测试都通过了!