測(cè)試驅(qū)動(dòng)開發(fā)案例之自動(dòng)售貨機(jī)(第1集)

          測(cè)試驅(qū)動(dòng)開發(fā)(TDD, Test Driven Development)是一種很有意思的軟件開發(fā)方式,本集以較小的步伐體驗(yàn)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è)計(jì)與模式》(Written by Cay Horstmann) Exercise 2.18
           
          設(shè)計(jì)并實(shí)現(xiàn)一個(gè)模擬自動(dòng)售貨機(jī)的程序。通過給機(jī)器投入正確的硬幣,便可以購(gòu)買到相應(yīng)的商品。用戶從一個(gè)可用的商品列表中選擇商品、投入硬幣并得到商品。如果沒有足夠的錢幣或由于此商品已銷售完畢,則將硬幣退回。操作員可以重新備并將錢幣取走。
           
           
          場(chǎng)景#1
          地點(diǎn):同濟(jì)大學(xué)西南樓
          涉眾:我,空自動(dòng)售貨機(jī)
           
          寢室樓里面剛搬來了一臺(tái)空的自動(dòng)售貨機(jī),那我們來測(cè)試一下:
           
          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;
          }
           
          全部保存,運(yùn)行測(cè)試,呵呵,綠色進(jìn)度條!測(cè)試成功!
           
          好,既然我們還沒有投過硬幣,那么余額應(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;
           
          運(yùn)行測(cè)試,yeah!
           
          看了一遍測(cè)試程序,決定優(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;
          }
          再運(yùn)行一次測(cè)試,呵呵,又是綠色!
           
          場(chǎng)景#2
          地點(diǎn):同濟(jì)大學(xué)西南樓
          涉眾:我,自動(dòng)售貨機(jī)(有一瓶百事可樂)
           
          好消息,樓長(zhǎng)在自動(dòng)售貨機(jī)里面放了一瓶Pepsi。
           
          public class TestAutomatWithOnePepsi extends TestCase
          {
            protected void setUp() throws Exception
            {
              super.setUp();
              automat = new Automat();
              pepsi = new Pepsi();  // 一瓶百事可樂
              automat.add(pepsi);  // 樓長(zhǎng)阿姨放的~
            }
            
            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)在有兩個(gè)TC(Test Case)了,為了運(yùn)行兩個(gè)測(cè)試案例,我們來創(chuàng)建一個(gè)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;
            }
          }
           
          編譯成功,運(yùn)行AutomatTests,紅色進(jìn)度條。TestEmptyAutomat綠色,TestAutomatWithOnePepsi紅色。呵呵,看來要讓add做點(diǎn)事情了。
           
          // class Automat
          public void add(Pepsi pepsi)
          {
            goods.add(pepsi);
          }
           
          // 添加一個(gè)裝Pepsi的數(shù)組列表
          private final ArrayList<Pepsi> goods = new ArrayList<Pepsi>();

           
          // 修改isEmpty方法
          public boolean isEmpty()
          {
            return goods.isEmpty();
          }

           
          再次運(yùn)行AutomatTests,呵呵,綠色!我們喜歡!
           
          好,我們?cè)倏纯碅utomat的余額:
           
          // class TestAutomatWithOnePepsi
          public void testBalance()
          {
            assertEquals("the balance should be 0.", 0, automat.balance);
          }
           
          運(yùn)行一遍測(cè)試,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;
          }
           
          我們太喜歡運(yùn)行測(cè)試了,于是又忍不住運(yùn)行了所有的自動(dòng)測(cè)試(呵呵,實(shí)際上我們只需要點(diǎn)擊一個(gè)運(yùn)行按鈕)。又是綠色~
           
          好,如果Pepsi的價(jià)格是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)
          {
           
          }
           
          運(yùn)行測(cè)試,綠色。顯然1塊錢買不到百事可樂。那就投2塊錢吧:
           
          // class TestAutomatWithOnePepsi
          public void testCanBuyWithTwoYuan()
          {
            automat.put(2);
            assertTrue("we can not buy pepsi with two yuan.", automat.canBuy(pepsi));
          }
           
          運(yùn)行測(cè)試,紅色進(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
           
          迫不及待地點(diǎn)擊了運(yùn)行按鈕,yeah!終于能買到喜歡的Pepsi了(因?yàn)榭吹搅司G色進(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呢?再添加一個(gè)測(cè)試:
           
          // 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;
            }
          }
           
          運(yùn)行測(cè)試,綠色!
          接下來投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.");
            } 
            // ...
          }

          好,沒有錯(cuò)誤提示了。編譯,運(yùn)行測(cè)試,紅色進(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");
          }
          測(cè)試通過了!小小慶祝一下~
           
          那如果我們投了三塊錢呢?好,試試看:
           
          // 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.");
            }  
          }
           
          編譯,運(yùn)行測(cè)試,成功!Yeah!
           
          To be continued...
           
           
          下一集將會(huì)更加精彩,敬請(qǐng)期待。
          posted on 2006-02-25 23:12 Baoquan Inside 閱讀(1767) 評(píng)論(3)  編輯  收藏 所屬分類: TDD

          評(píng)論

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

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

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

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

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

          第2集呢?

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


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 水富县| 博兴县| 盐山县| 天津市| 社旗县| 廉江市| 玛纳斯县| 通榆县| 武城县| 安顺市| 雷山县| 泾阳县| 永德县| 韩城市| 镇雄县| 峨眉山市| 华池县| 马龙县| 亚东县| 思南县| 永州市| 岱山县| 兰坪| 宁明县| 兴国县| 牙克石市| 会宁县| 南宁市| 彰武县| 雷州市| 托里县| 绩溪县| 海南省| 河东区| 望奎县| 东源县| 大姚县| 沙湾县| 南投市| 朝阳县| 葫芦岛市|