测试驱动开发(TDD, Test Driven Development)是一种很有意思的软件开发方式,本集以较小的步伐体验TDD。
/** A program simulation of an automat.
*
* Director: Zuo Baoquan
* Contact me:
* Email: Baoquan.Zuo [at] gmail.com
*
* Copyright 2006 BEC Studio
*/
引言:
设计并实现一个模拟自动售货机的程序。通过给机器投入正确的硬币,便可以购买到相应的商品。用户从一个可用的商品列表中选择商品、投入硬币并得到商品。如果没有足够的钱币或由于此商品已销售完毕,则将硬币退回。操作员可以重新备货并将钱币取走。
场景#1
地点:同济大学西南楼
涉众:我,空自动售货机
寝室楼里面刚搬来了一台空的自动售货机,那我们来测试一下:
public class TestEmptyAutomat extends TestCase
{
public void testIsEmpty()
{
Automat automat = new Automat();
assertTrue("it should be empty.", automat.isEmpty());
}
}
Eclipse提示"Automat cannot be resolved to a type",好,看我的:
public class Automat
{
public Automat() // constructor stub
{
}
}
再露一手:
//class Automat
public boolean isEmpty()
{
return true;
}
全部保存,运行测试,呵呵,绿色进度条!测试成功!
好,既然我们还没有投过硬币,那么余额应该是0了~
// class TestEmptyAutomat
public void testBalance()
{
Automat automat = new Automat();
assertEquals("the balance should be 0.", 0, automat.balance);
}
继续使用我们的法宝:
// class Automat
public final int balance = 0;
运行测试,yeah!
看了一遍测试程序,决定优化一下:
public class TestEmptyAutomat extends TestCase
{
protected void setUp() throws Exception
{
super.setUp();
automat = new Automat();
}
public void testIsEmpty()
{
assertTrue("it should be empty.", automat.isEmpty());
}
public void testBalance()
{
assertEquals("the balance should be 0.", 0, automat.balance);
}
private Automat automat;
}
再运行一次测试,呵呵,又是绿色!
场景#2
地点:同济大学西南楼
涉众:我,自动售货机(有一瓶百事可乐)
好消息,楼长在自动售货机里面放了一瓶Pepsi。
public class TestAutomatWithOnePepsi extends TestCase
{
protected void setUp() throws Exception
{
super.setUp();
automat = new Automat();
pepsi = new Pepsi(); // 一瓶百事可乐
automat.add(pepsi); // 楼长阿姨放的~
}
public void testEmpty()
{
assertFalse("it should not be empty.", automat.isEmpty());
}
public Automat automat;
public Pepsi pepsi;
}
接着创建Pepsi类
public class Pepsi
{
public Pepsi() // constructor stub
{
}
}
再给Automat添加add方法:
// class Automat
public void add(Pepsi pepsi)
{
}
好,现在有两个TC(Test Case)了,为了运行两个测试案例,我们来创建一个Test Suite:
public class AutomatTests
{
public static Test suite()
{
TestSuite suite = new TestSuite("Test for net.mybec.automat");
//$JUnit-BEGIN$
suite.addTestSuite(TestAutomatWithOnePepsi.class);
suite.addTestSuite(TestEmptyAutomat.class);
//$JUnit-END$
return suite;
}
}
编译成功,运行AutomatTests,红色进度条。TestEmptyAutomat绿色,TestAutomatWithOnePepsi红色。呵呵,看来要让add做点事情了。
// class Automat
public void add(Pepsi pepsi)
{
goods.add(pepsi);
}
// 添加一个装Pepsi的数组列表
private final ArrayList<Pepsi> goods = new ArrayList<Pepsi>();
// 修改isEmpty方法
public boolean isEmpty()
{
return goods.isEmpty();
}
再次运行AutomatTests,呵呵,绿色!我们喜欢!
好,我们再看看Automat的余额:
// class TestAutomatWithOnePepsi
public void testBalance()
{
assertEquals("the balance should be 0.", 0, automat.balance);
}
运行一遍测试,Ok。
我们还没有投硬币,当然不能买百事可乐了:
// class TestAutomatWithOnePepsi
public void testCanBuyWithoutBalance()
{
assertFalse("we cannot buy pepsi without money.", automat.canBuy(pepsi));
}
// class Automat
public boolean canBuy(Pepsi pepsi)
{
return false;
}
我们太喜欢运行测试了,于是又忍不住运行了所有的自动测试(呵呵,实际上我们只需要点击一个运行按钮)。又是绿色~
好,如果Pepsi的价格是2元,我们投1块钱试试~
// class Pepsi
public static final int PRICE = 2;
// class TestAutomatWithOnePepsi
public void testCanBuyWithOneYuan()
{
automat.put(1);
assertFalse("we cannot buy pepsi with one yuan.", automat.canBuy(pepsi));
}
// class Automat
public void put(int yuan)
{
}
运行测试,绿色。显然1块钱买不到百事可乐。那就投2块钱吧:
// class TestAutomatWithOnePepsi
public void testCanBuyWithTwoYuan()
{
automat.put(2);
assertTrue("we can not buy pepsi with two yuan.", automat.canBuy(pepsi));
}
运行测试,红色进度条,
JUnit提示“we can not buy pepsi with two yuan.” 天啊,这不公平。
想起来了,Automat.put什么也没做。于是我们添了几笔:
// class Automat
public void put(int yuan)
{
balance += yuan;
}
public boolean canBuy(Pepsi pepsi)
{
return balance >= Pepsi.PRICE;
}
public int balance = 0; // 去掉了final
迫不及待地点击了运行按钮,yeah!终于能买到喜欢的Pepsi了(因为看到了绿色进度条~)。
于是急忙买了一瓶Pepsi:
// class TestAutomatWithOnePepsi
public void testBuyPepsiWithTwoYuan()
{
automat.put(2);
automat.buy(pepsi);
assertTrue("the automat should be empty.", automat.isEmpty());
}
// class Automat
public void buy(Pepsi pepsi)
{
}
Run Tests, Failed. So,
// class Automat
public void buy(Pepsi pepsi)
{
goods.remove(pepsi);
}
Run Tests again, OK.
// class TestAutomatWithOnePepsi
public void testBuyPepsiWithTwoYuan()
{
automat.put(2);
automat.buy(pepsi);
assertTrue("the automat should be empty.", automat.isEmpty());
assertEquals("the balance should be 0.", 0, automat.balance);
}
Run Tests, failed, so
// class Automat
public void buy(Pepsi pepsi)
{
goods.remove(pepsi);
balance -= Pepsi.PRICE;
}
Run Tests again, OK.
那如果没有投币就直接买Pepsi呢?再添加一个测试:
// class TestAutomatWithOnePepsi
public void testBuyPepsiWithNoBalance()
{
try
{
automat.buy(pepsi);
fail("a NoBalanceException was expected when buying a pepsi with no balance.");
}
catch (NoBalanceException e)
{
}
}
// class NoBalanceException
public class NoBalanceException extends Exception
{
public NoBalanceException(String arg0)
{
super(arg0);
}
}
// class TestAutomatWithOnePepsi
public void testBuyPepsiWithTwoYuan()
{
automat.put(2);
try
{
automat.buy(pepsi);
}
catch (NoBalanceException e)
{
fail("a NoBalanceException was throwed.");
}
assertTrue("the automat should be empty.", automat.isEmpty());
assertEquals("the balance should be 0.", 0, automat.balance);
}
// class Automat
public void buy(Pepsi pepsi) throws NoBalanceException
{
if (balance == 0)
throw new NoBalanceException("No balance");
else if (balance >= Pepsi.PRICE)
{
goods.remove(pepsi);
balance -= Pepsi.PRICE;
}
}
运行测试,绿色!
接下来投1块钱买买看:
// class TestAutomatWithOnePepsi
public void testBuyPepsiWithOneYuan()
{
automat.put(1);
try
{
automat.buy(pepsi);
fail("a LackOfBalanceException was expected when buying a pepsi without enough balance.");
}
catch (LackOfBalanceException e)
{
}
catch (NoBalanceException e)
{
fail("a NoBalanceException was not expected.");
}
}
接下来,为了能使编译通过,做了一下修改:
// class LackOfBalanceException
public class LackOfBalanceException extends Exception
{
public LackOfBalanceException(String arg0)
{
super(arg0);
}
}
// class Automat
public void buy(Pepsi pepsi) throws NoBalanceException, LackOfBalanceException
{
// ...
}
// class TestAutomatWithOnePepsi
public void testBuyPepsiWithNoBalance()
{
// ...
catch (LackOfBalanceException e)
{
fail("a LackOfBalanceException was not expected.");
}
}
public void testBuyPepsiWithTwoYuan()
{
// ...
catch (LackOfBalanceException e)
{
fail("a LackOfBalanceException was not expected.");
}
// ...
}
好,没有错误提示了。编译,运行测试,红色进度条。
testBuyPepsiWithOneYuan()提示“a LackOfBalanceException was expected when buying a pepsi with no enough balance."
我们修改一下Automat.buy():
// class Automat
public void buy(Pepsi pepsi) throws NoBalanceException, LackOfBalanceException
{
if (balance == 0)
{
throw new NoBalanceException("No balance");
}
else if (balance >= Pepsi.PRICE)
{
goods.remove(pepsi);
balance -= Pepsi.PRICE;
}
else
throw new LackOfBalanceException("Lack of Balance");
}
测试通过了!小小庆祝一下~
那如果我们投了三块钱呢?好,试试看:
// class TestAutomatWithOnePepsi
public void testBuyPepsiWithThreeYuan()
{
automat.put(3);
assertTrue("we can buy pepsi.", automat.canBuy(pepsi));
try
{
automat.buy(pepsi);
assertTrue("the automat should be empty.", automat.isEmpty());
assertEquals("the balance should be 1.", 1, automat.balance);
}
catch (Exception e)
{
fail("Exception was not expected.");
}
}
编译,运行测试,成功!Yeah!
To be continued...
下一集将会更加精彩,敬请期待。