測試驅(qū)動開發(fā)案例之自動售貨機(第1集)

          測試驅(qū)動開發(fā)(TDD, Test Driven Development)是一種很有意思的軟件開發(fā)方式,本集以較小的步伐體驗TDD。

          /**  A program simulation of an automat.
           *
           *  Director: Zuo Baoquan
           *  Contact me:
           *     Blog:   http://www.aygfsteel.com/BaoquanInside
           *     MSN: Baoquan.Zuo [at] hotmail.com
           *     Email:  Baoquan.Zuo [at] gmail.com
           *
           *  Copyright 2006 BEC Studio
           */
           
          引言:
          (該問題截取自《面向?qū)ο蟮脑O(shè)計與模式》(Written by Cay Horstmann) Exercise 2.18
           
          設(shè)計并實現(xiàn)一個模擬自動售貨機的程序。通過給機器投入正確的硬幣,便可以購買到相應(yīng)的商品。用戶從一個可用的商品列表中選擇商品、投入硬幣并得到商品。如果沒有足夠的錢幣或由于此商品已銷售完畢,則將硬幣退回。操作員可以重新備并將錢幣取走。
           
           
          場景#1
          地點:同濟(jì)大學(xué)西南樓
          涉眾:我,空自動售貨機
           
          寢室樓里面剛搬來了一臺空的自動售貨機,那我們來測試一下:
           
          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;
          }
           
          全部保存,運行測試,呵呵,綠色進(jìn)度條!測試成功!
           
          好,既然我們還沒有投過硬幣,那么余額應(yīng)該是0了~
           
          // class TestEmptyAutomat
          public void testBalance()
          {
            Automat automat = new Automat();
            assertEquals("the balance should be 0.", 0, automat.balance); 
          }
           
          繼續(xù)使用我們的法寶:
          // class Automat
          public final int balance = 0;
           
          運行測試,yeah!
           
          看了一遍測試程序,決定優(yōu)化一下:
          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
          地點:同濟(jì)大學(xué)西南樓
          涉眾:我,自動售貨機(有一瓶百事可樂)
           
          好消息,樓長在自動售貨機里面放了一瓶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;
          }

          接著創(chuàng)建Pepsi類
           
          public class Pepsi
          {
            public Pepsi() // constructor stub
            {
             
            }
          }
           
          再給Automat添加add方法:
          // class Automat
          public void add(Pepsi pepsi)
          {
           
          }
           
          好,現(xiàn)在有兩個TC(Test Case)了,為了運行兩個測試案例,我們來創(chuàng)建一個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,紅色進(jìn)度條。TestEmptyAutomat綠色,TestAutomatWithOnePepsi紅色。呵呵,看來要讓add做點事情了。
           
          // class Automat
          public void add(Pepsi pepsi)
          {
            goods.add(pepsi);
          }
           
          // 添加一個裝Pepsi的數(shù)組列表
          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。
           
           
          我們還沒有投硬幣,當(dāng)然不能買百事可樂了:
           
          // 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));
          }
           
          運行測試,紅色進(jìn)度條,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了(因為看到了綠色進(jìn)度條~)。
           
          于是急忙買了一瓶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.");
            } 
            // ...
          }

          好,沒有錯誤提示了。編譯,運行測試,紅色進(jìn)度條。
          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...
           
           
          下一集將會更加精彩,敬請期待。
          posted on 2006-02-25 23:12 Baoquan Inside 閱讀(1766) 評論(3)  編輯  收藏 所屬分類: TDD

          評論

          # re: 測試驅(qū)動開發(fā)案例之自動售貨機(第1集) 2006-02-27 02:56 dev  回復(fù)  更多評論   

          你的這個例子存在并發(fā)問題,一個是金額,另一個是商品都有這個問題;另外,一個自動售貨機可能會有幾種商品,這個沒有考慮到

          # re: 測試驅(qū)動開發(fā)案例之自動售貨機(第1集) 2006-02-27 10:32 Baoquan Inside  回復(fù)  更多評論   

          @dev
          謝謝你的關(guān)注!我在并發(fā)編程方面幾乎沒有經(jīng)驗,所以例子可能存在你說提到的并發(fā)問題。為了寫這篇文章,我一直在Blog和Eclipse之間換來換去。不過故事才剛剛開始,所以,你提到的“多種商品”問題將在下一集中被解決。

          # re: 測試驅(qū)動開發(fā)案例之自動售貨機(第1集) 2007-09-16 16:48 魚上游  回復(fù)  更多評論   

          第2集呢?

          只有注冊用戶登錄后才能發(fā)表評論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 同德县| 天等县| 麻城市| 遂平县| 淮南市| 通海县| 木兰县| 墨竹工卡县| 镇安县| 安国市| 海宁市| 枞阳县| 塔河县| 左权县| 二连浩特市| 绵竹市| 南江县| 信宜市| 方城县| 开封县| 临泉县| 峨山| 舟曲县| 醴陵市| 绥化市| 民乐县| 米林县| 洪泽县| 谢通门县| 博兴县| 讷河市| 鄢陵县| 玉溪市| 沈阳市| 张家界市| 西城区| 太仓市| 汝州市| 永善县| 简阳市| 龙陵县|