Test a little ,code a little ,实战junit
Notyy的junit教程(一)
有了junit,java程序的单元测试变成非常简单的事。而在写代码前先写测试这个似乎不可思议的规定也变的十分合理而且能有效的帮助程序的设计。
下面是一个accountbean的例子,假设我们要实现一个帐户管理程序,能够向帐户中增加、减少金额,还能在帐户间转帐。很显然需要有一个account类,且慢写代码!在写代码前先写测试,测试如何写?假设你已经有这个account类,你希望如何使用它?显然首先要初始化它,一个帐户应该知道他的拥有者,和他现有的金额,那么他应该有个构建器形如:
account(String Owner,double Balance);那就先写测试这个构建器的代码,如下:
package account;
import junit.framework.TestCase;
public class AccountTest extends TestCase {
private Account AccountA;
private Account AccountB;
public AccountTest(String name) {
super(name);
}
public static void main(String args[]) {
junit.textui.TestRunner.run(AccountTest.class);
}
public void testAccount(){
AccountA=new Account("notyy",100);
AccountB=new Account("bricks",200);
assertEquals("notyy",AccountA.Owner);
assertEquals(100,AccountA.Balance,2);
assertEquals("bricks",AccountB.Owner);
assertEquals(200,AccountB.Balance,2);
}
}这段代码无法编译,因为没有account类,我们来为account类写个骨架,使他能够编译。代码如下:
package account;
public class Account {
private String Owner;
private double Balance;
public Account(String aOwner,double aBalance) {
}
}
编译后,写个批命令来运行测试程序,如下:
test.bat
java -cp %classpath%;d:\javaprj\account\classes account.AccountTest
别忘了把junit.jar包含在你的classpath里,junit的配置这里不再多说。
运行test.bat得到如下结果
FAILURES!!!
Test Results:
Run:1 Failure :1 Errors:0
There was 1 failure
1) testAccount(account.AccountTest) “expected . but was ”
感觉如何,测试程序得出了我们预料的结果,因为构建器还没有实现呢!
现在我们来实现构建器
public Account(String aOwner,double aBalance) {
Owner=aOwner;
Balance=aBalance;
}
得到结果:
OK!
测试通过,说明我们的构建器没有问题!
现在我们来refactoring一下,考虑上面的类有什么问题?owner和balance不应该能直接被外界访问的吧。把他们改成两个私有变量,然后用2个get方法来读取他们。尽管xp explore上建议连getxxx和setxxx方法也该测试,我个人是觉得这样过于麻烦的:)所以就省了。如果用get方法的话测试程序需要改变一下
改成形如:assertEquals("notyy",AccountA.getOwner());就行了。
为account类增加2个get方法:
public String getOwner(){
return Owner;
}
public double getBalance(){
return Balance;
}
编译,运行test.bat
OK!一次通过。咱们继续。Account类可以给自己的帐户里加钱(credit),方法形如 Account.credit(double aMoney);
先写testcredit
public void testCredit(){
AccountA=new Account("notyy",100);
AccountB=new Account("bricks",200);
AccountA.credit(100);
//100+100=200
assertEquals(200,AccountA.getBalance(),2);
AccountB.credit(150);
//200+150=350
assertEquals(350,AccountB.getBalance(),2);
}
无法编译,因为account类没有credit方法,咱们给他加个方法骨架:
public void credit(double aMoney){
}
编译通过,运行test.bat,失败:
“expected <200.0> but <100.0>”
现在实现credit方法,如下:
public void credit(double aMoney){
Balance+=aMoney;
}
再次编译运行,OK!(2 tests)
哇,转眼写了这么长的文章了:)
今天累了。明天再写。
继续:)
再来实现扣减方法,和增加几乎一样,原样处理。
先写test
public void testDiscount(){
AccountA=new Account("notyy",100);
AccountB=new Account("bricks",200);
AccountA.discount(50);
//100-50=50
assertEquals(50.00,AccountA.getBalance(),2);
AccountB.discount(120);
//200-120=80
assertEquals(80,AccountB.getBalance(),2);
}
然后实现
public void discount(double aMoney){
Balance-=aMoney;
}
最后是转帐功能,转帐是从一个帐户转到另一个帐户。其实是调用一个帐户的增加功能和另一个帐户的减少功能。
每个测试里都要建立accountA和accountB是不是很烦,junit考虑到了这一点,所以可以覆盖testcase的setUp方法,在该方法内建立一些所有test都要用到的变量等。
public void setUp(){
AccountA=new Account("notyy",100);
AccountB=new Account("bricks",200);
}
这样,所有的测试方法中都不用再建立这两个实例了。:)
好,写转帐方法的测试
public void testTransfer(){
AccountA.transfer(AccountB,80.00);
//100-80=20
//200+80=280
assertEquals(20.00,AccountA.getBalance(),2);
assertEquals(280.00,AccountB.getBalance(),2);
}
然后建立transfer方法的框架,使它能编译:
public void transfer(Account aAccount,double aBalance){}
测试时报失败,expected “20” but was “100”
然后填入实现 :
public void transfer(Account aAccount,double aBalance){
this.discount(aBalance);
aAccount.credit(aBalance);
}
test OK!
简单的步骤,却可使你对你实现的方法的正确性确信无疑,而且写测试的过程也是设计的过程,如果在写一个方法前,你连应该得到的输出都想不明白,又怎么能动手去写呢?
谁说XP只要code,不要设计呢? :)
好了,junit单元测试的第一个例子就写到这吧。很简单吧?