測試驅動開發(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...
下一集將會更加精彩,敬請期待。