<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    零雨其蒙's Blog

    做優(yōu)秀的程序員
    隨筆 - 59, 文章 - 13, 評論 - 58, 引用 - 0
    數(shù)據(jù)加載中……

    零雨其蒙:Practicing Test-Driven Development by Example Using Delphi

     

    Practicing Test-Driven Development by Example Using Delphi

                        零雨其蒙原創(chuàng) 轉(zhuǎn)載請注明

    1       測試驅(qū)動開發(fā)

    測試驅(qū)動開發(fā)不是什么噱頭,而是真正有用的開發(fā)實踐。今天派給我一個任務(wù),讓我解決一下退休提醒功能的Bug,我沒看出原來的代碼有何錯誤,不過覺得設(shè)計思路不十分的好:將數(shù)據(jù)庫中所有的員工都取出來,然后再篩選應(yīng)該被提醒的員工。而我覺得應(yīng)該直接到數(shù)據(jù)庫中去做篩選,返回大量無用的數(shù)據(jù)是資源的巨大浪費(我們在甲方公司里面開發(fā),其網(wǎng)絡(luò)之差令人發(fā)指)。

    正好,我在研究TDD(本文有時指的是測試驅(qū)動開發(fā),有時指的是測試驅(qū)動設(shè)計,因為兩者都是存在的),想想何不來一次徹底的實踐,真正的、完整的來一次TDD,體味一下其中的樂趣。

    由于我們的開發(fā)工具是Delphi,因此自動測試工具自然而然就要使用DUnit了。盡管有的人說TDD不一定非得用自動測試框架,我也在使用VB進行OO系統(tǒng)開發(fā)時,用自制的測試程序進行測試,不過覺得那樣都有一種不爽的感覺。因為總需要去維護復(fù)雜的測試代碼,不能全力投入到測試驅(qū)動設(shè)計中。

    2       準備工作

    首先介紹一下業(yè)務(wù):

    很簡單,就是在一個界面上顯示即將退休的人員,具體提前多少天顯示是從數(shù)據(jù)庫中讀取的參數(shù)。

    然后配置DUnit環(huán)境,網(wǎng)上有n多教程,然后安裝了一個DUnit plug-in插件,方便開發(fā),網(wǎng)上也有講解。

    Stop on Delphi Exception前的對號取消,這樣就不會在出現(xiàn)異常時跳出了。

    3       開始TDD之旅

    本文是我進行TDD的實踐記錄,當然其間的思考要比這個多一些,不過主體部分基本都包含了,而且絕對寫實。本文不是TDD的頌歌,我也提出了自己在實踐中遇到的困難和疑惑。希望能給讀者帶來啟示。

    創(chuàng)建工程文件HR.dpr,然后使用DUnit plug-inNew Project,就自動在HR.dpr所在文件夾建了一個dunit文件夾,新建的測試工程默認名為HRTests,這是很好的規(guī)范,默認即可。然后New TestModule,建立一個測試單元。

    接下來的工作就是在這兩個同時開著的工程中開始工作了,一會我會切換到HRTests編寫測試用例,一會我會在HR下編寫產(chǎn)品代碼,然后再回到HRTests下運行Dunit,進行測試。

    3.1 領(lǐng)域驅(qū)動設(shè)計

       首先構(gòu)建領(lǐng)域?qū)樱I(lǐng)域概念就是退休(Retired),退休人員(EmployeeRetired)了。

       先創(chuàng)建這兩個類,不少文章說先建立測試用例,然后測試時肯定顯示紅條,因為被測試的類還沒有建立,我覺得沒建立的話連編譯都過不了,怎么運行DUnit啊?

       然后就可以開始根據(jù)想象編寫測試用例了。思考對象的責任和工作方式,然后切換到產(chǎn)品工程添加這些責任。(有點像一邊畫順序圖一邊畫類圖進行責任分配) 

    首先,我創(chuàng)建類TRetire

    TRetire類分配一個責任:查找退休提醒參數(shù):

    { 作者:零雨其蒙

    創(chuàng)建時間:2007-5-24

    Blog:blog.csdn.net/sslaowan

        m.tkk7.com/sslaowan

    }

     

    function TRetire.getretireAwokeParaList: TObjectList;

    var paraList:TObjectList;

    begin

     

       

    end;

       這是一個空殼,沒有實際的內(nèi)容,然后切換到HRTest工程,會出現(xiàn)下面的對話框。

      

       HR工程做了任何改動,保存后,都會在HRTest中有提醒。

       可能很多人從來沒見過測試用例長什么樣子,下面就給出一個完整的例子。

    { 作者:零雨其蒙

    創(chuàng)建時間:2007-5-24

    Blog:blog.csdn.net/sslaowan

        m.tkk7.com/sslaowan

    }

     

    unit HRTestsTests;

     

    interface

     

    uses

     TestFrameWork,

     URetire,

     Contnrs;

     

    type

       TTestRetire=class(TTestCase)

       private

          retire:TRetire;

          retirePara:TRetirePara;

       protected

            procedure SetUp; override;

            procedure TearDown; override;

       published

            procedure testGetretireAwokeParaList;

       end;

     

    implementation

     

    function UnitTests: ITestSuite;

    var

     ATestSuite: TTestSuite;

    begin

     ATestSuite := TTestSuite.Create('Retire tests');

     ATestSuite.AddTests(TTestRetire);

     Result := ATestSuite;

    end;

     

    { TTestRetire }

     

    procedure TTestRetire.SetUp;

    begin

     inherited;

       retire:=TRetire.Create;

       retirePara:=TRetirePara.Create;

    end;

     

    procedure TTestRetire.TearDown;

    begin

     inherited;

     retire.Free;

     retirePara.Free;

    end;

     

    procedure TTestRetire.testGetretireAwokeParaList;

    var paraList:TObjectList;

    begin

       paraList:=retire.getretireAwokeParaList;

       retirePara:=TRetirePara(paraList.Items[0]);

       check(retirePara._emp_type='管理人');

       check(retirePara._sex='');

       check(retirePara._retireage='60');

       check(retirePara._uptime='90');

     

       retirePara:=TRetirePara(paraList.Items[1]);

       check(retirePara._emp_type='管理人');

       check(retirePara._sex='');

       check(retirePara._retireage='55');

       check(retirePara._uptime='90');

     

       retirePara:=TRetirePara(paraList.Items[2]);

       check(retirePara._emp_type='工人');

       check(retirePara._sex='');

       check(retirePara._retireage='60');

       check(retirePara._uptime='90');

     

       retirePara:=TRetirePara(paraList.Items[3]);

       check(retirePara._emp_type='工人');

       check(retirePara._sex='');

       check(retirePara._retireage='50');

       check(retirePara._uptime='90');

    end;

     

    initialization

         RegisterTest('Retire test',UnitTests);

     

    end.

    在編寫測試用例時,我發(fā)現(xiàn)返回的不是一個一維的表,而是二維的,我還是使用了對象來報存另一維的數(shù)據(jù),又創(chuàng)建了一個名為TRetirePara的類。其屬性如上面的代碼所示。

        

    然后編譯運行HRTest,出現(xiàn)DUnit,點擊綠色的RUN按鈕,出現(xiàn)紅條。錯誤是EAccessViolation

     

       這是因為沒有創(chuàng)建TObjectlist的實例paraList,而在testGetretireAwokeParaList中訪問了它,這時切換到HR工程,在getretireAwokeParaList中添加如下代碼。

    { 作者:零雨其蒙

    創(chuàng)建時間:2007-5-24

    Blog:blog.csdn.net/sslaowan

        m.tkk7.com/sslaowan

    }

     

    function TRetire.getretireAwokeParaList: TObjectList;

    var paraList:TObjectList;

    begin

        paraList:=TObjectList.Create;

     

        getretireAwokeParaList:=paraList;

     

    end;

       再次測試。

     

    依然顯示紅色,錯誤是List index out of bounds,這是因為在getretireAwokeParaList方法中并沒有向paraList中添加任何對象,這時需要再對getretireAwokeParaList進行重構(gòu)。

     

    3.2 步伐到底要多小?

    當有了“測試驅(qū)動依賴癥”后,就想讓DUnit幫我思考一些內(nèi)容,比如下一步應(yīng)該編寫什么。通過測試,我不斷的清楚了自己下一步的任務(wù)。但是或許不需要如此小步的前進,如果已經(jīng)有了很好的思路,可以一下子把剛才的程序都編完,甚至把整個getretireAwokeParaList方法都完成。

    然而由于沒有太多的plan,想一點編一點的,難免就會采取這樣小的步伐,其實這樣也很好,因為不至于寫了一大堆,錯了都不知道哪一句是罪魁禍首。經(jīng)常看到有人在面對一堆不知道在哪個地方出錯的代碼時,采用了刪除所有,然后一句一句還原,發(fā)現(xiàn)到哪句錯誤就改哪句。這種做法一般都被用于沒有調(diào)試器的環(huán)境,比如HTML頁面。如果有了調(diào)試器,傳統(tǒng)的做法當然是設(shè)置斷點,然后利用調(diào)試器進行跟蹤。關(guān)掉調(diào)試器,以測試代替調(diào)試的一個支撐點是,不大可能會出現(xiàn)大段的需要你去跟蹤錯誤的代碼,因為很小的一步重構(gòu)進行之后,就開始測試了,哪里有錯誤一目了然。當然調(diào)試器的作用并不只是跟蹤某個變量在運行時的值的變化,還有理解代碼在匯編一級上是如何工作的,這將更加有利于你調(diào)錯。但是,總而言之,調(diào)試器肯定是幫助你調(diào)試錯誤的,小步前進的單元測試可以幫助你在不使用調(diào)試器的情況下,找出錯誤。

    3.3 混入持久層的測試

    或許看到這篇文章的您,有更好的方法來完成這樣的測試,請您告訴我,因為我也是使用Dunit進行TDD的新手,希望分享您的寶貴經(jīng)驗。

    我們使用的是ODAC控件連接ORACLE數(shù)據(jù)庫。在產(chǎn)品代碼(HR.dpr)中,通常我們都是直接加載數(shù)據(jù)模塊中TOraSession,來連接數(shù)據(jù)庫,再用TOraQuery與之相連。getretireAwokeParaList的主要操作是從數(shù)據(jù)庫中獲取記錄,產(chǎn)品代碼如下:

    { 作者:零雨其蒙

    創(chuàng)建時間:2007-5-24

    Blog:blog.csdn.net/sslaowan

        m.tkk7.com/sslaowan

    }

     

    function TRetire.getretireAwokeParaList: TObjectList;

    var paraList:TObjectList;

        retirePara:TRetirePara;

    begin

        paraList:=TObjectList.Create;

     

     

        _qry.Close;

        _qry.SQL.Clear;

        _qry.SQL.Text:='select * from HR1_RETIREPARAMETER';

        _qry.Open;

     

        if _qry.RecordCount>0 then

        begin

          _qry.First;

     

          while not _qry.Eof do

          begin

            retirePara:=TRetirePara.Create;

            retirePara._emp_type:=_qry.FieldByName('EMPLOYEE_TYPE').AsString;

            retirePara._sex:=_qry.FieldByName('SEX').AsString;

            retirePara._retireage:=_qry.FieldByName('RETIRE_AGE').AsString;

            retirePara._uptime:=_qry.FieldByName('UPTIME_DAYS').AsString;

     

            paraList.Add(retirePara) ;

     

            _qry.Next;

          end;

        end;

     

        getretireAwokeParaList:=paraList;

     

    end;

       這比你在上一節(jié)看到的代碼又豐富了許多。_qry是用到的TOraQuery類型的變量,它接受TOraQuery實例。由于進行單元測試時,HR.dpr是不啟動的,因此DM根本就不會被創(chuàng)建。

       考慮再三,我在測試項目HRTest.dpr中加入了數(shù)據(jù)庫連接代碼,并將創(chuàng)建的TOraQuery實例賦值給TRetire的屬性(propertyqry。代碼如下:

    { 作者:零雨其蒙

    創(chuàng)建時間:2007-5-24

    Blog:blog.csdn.net/sslaowan

        m.tkk7.com/sslaowan

    }

     

    procedure TTestRetire.SetUp;

    begin

     inherited;

       _qry:=TOraQuery.Create(nil);

     { _session:=TOraSession.Create(nil);

       _session.Server:=';

       _session.ConnectString:='';

       _session.Username:='';

       _session.Password:='';

       _session.ConnectPrompt:=false;

       _session.Connect; }

       _dmhr:=TDMHR.Create(nil);

       _session:=TOraSession.Create(nil);

       _session:= _dmhr.HRSession ;

       _qry.Session:=_session;

     

       retire:=TRetire.Create;

       retire.qry:=_qry;

    end;

    起初,我創(chuàng)建了TOraSession對象,可是不知道為什么說驅(qū)動器有錯誤,于是就創(chuàng)建了一個DM(數(shù)據(jù)模塊),然后獲得其中的HRSession。注釋掉的代碼有何錯誤還請高人指點。之后我又寫了個測試連接是否成功的方法。

    { 作者:零雨其蒙

    創(chuàng)建時間:2007-5-24

    Blog:blog.csdn.net/sslaowan

        m.tkk7.com/sslaowan

    }

     

    procedure TTestRetire.testConnect;

    begin

       check(_session.Connected=true);

    end;

     

       之后再進行測試,綠條終于出現(xiàn)了!

     

    我在之前還犯了錯誤,總是出現(xiàn)紅條。后來才發(fā)現(xiàn)原來對象創(chuàng)建沒搞清楚。這個錯誤在我編寫Java程序時也犯過,看來一定要注意阿。

    { 作者:零雨其蒙

    創(chuàng)建時間:2007-5-24

    Blog:blog.csdn.net/sslaowan

        m.tkk7.com/sslaowan

    }

     

    while not _qry.Eof do

          begin

            retirePara:=TRetirePara.Create;

            retirePara._emp_type:=_qry.FieldByName('EMPLOYEE_TYPE').AsString;

            //省略若干行

            paraList.Add(retirePara) ;

     

            _qry.Next;

          end;

        end;

    原來將retirePara:=TRetirePara.Create;這句寫在循環(huán)之外了,結(jié)果測試時,只有最后一條(paraList.Item[3]對應(yīng)的結(jié)果)是正確的。這個錯誤大家一看就知道了,每循環(huán)一次都需要創(chuàng)建一個retirePara對象,要不然向paraList添加的其實都是一個對象,而paraList的每個元素都是指向同一個對象引用,賦值之后,當然每個對象的屬性值都是一樣的啦。

    還有,在測試用例中進行比較時,我剛開始圖省事,都使用check,結(jié)果錯了后沒有任何提示,后來到TestFramework中查到了CheckEquals方法,這個方法很好,如果出錯了,會告訴你期望值是什么,實際值是什么。

    3.4 進化式設(shè)計

    TDD韻律操是:編寫單元測試——〉測試,紅條——〉編寫產(chǎn)品代碼——〉綠條——〉編寫單元測試——〉測試,紅條——〉編寫產(chǎn)品代碼——〉綠條,編寫產(chǎn)品代碼并不能總是一氣呵成,因此就會在編寫部分產(chǎn)品代碼——〉綠條——〉重構(gòu)——〉測試,綠條/紅條——〉重構(gòu)——〉測試,綠條/紅條

     

        下面開始編寫function retireAwoke(qry:TOraQuery):TObjectList;方法。還是先寫測試用例。   

         這里面有個問題,這個方法的作用是查詢并返回所有的符合退休條件的員工,每天的人可能都不一樣,那么該怎樣寫測試用例呢?這個或許應(yīng)該從DUnit的測試裝備中讀取(如果DUnit有的話,我也不知道有沒有),也可以從一個文本文件或者Excel中讀取,這樣或許好些。但是這依然不是一個可回歸測試。

    最終想的辦法是通過SQL語句先查詢一下,看看有哪些記錄,而且這個SQL語句與程序中的不盡相同。主要是將計算退休者生日的程序?qū)懺诹?/span>SQL中還是寫在程序中的區(qū)別。

    使用如下的SQL語句進行查詢:

    select employeeid,employeename,dptname,birthday,sex,EMPLOYEETYPE

        from HR1_EMPLOYEE left join hr1_workdept on hr1_employee.workdeptid=hr1_workdept.dptid

        where sex='' and EMPLOYEETYPE='管理人員'

        and BIRTHDAY between '1947-05-24' and (select to_char(to_date('1947-05-24','yyyy-mm-dd') + interval '90' day,'yyyy-mm-dd')

        from dual) order by dptid asc;

    用于返回60歲退休的男性管理人員(提前90天提醒)。以下是SQL Plus的查詢結(jié)果。

    EMPLOYEEID EMPLOYEENA DPTNAME BIRTHDAY   SE EMPLOYEETY

    ---------- ---------- ------------------- ---------- -- ----------

    YG000043   張三豐    人力資源部           1947-04-16 管理人員

    已選擇 1

    還需要說明的是,有四種情況需要測試,但是為了快速實現(xiàn),我只是寫了其中一種情況,即男,管理人員。然后我就寫了測試程序。

    { 作者:零雨其蒙

    創(chuàng)建時間:2007-5-24

    Blog:blog.csdn.net/sslaowan

        m.tkk7.com/sslaowan

    }

    procedure TTestRetire.testRetireAwoke;

    var employeeRetiredList:TObjectList;

            i:integer;

    begin

          employeeRetiredList:=TObjectList.Create;

           _employeeRetired:=TEmployeeRetired(employeeRetiredList.Items[0]);

          CheckEquals(' YG000043',_employeeRetired._ID);

    CheckEquals('張三豐',_employeeRetired._Name);

    end;

    產(chǎn)品代碼只是讀出了參數(shù)列表的第一種情況。然后嵌套進SQL語句中進行查詢。

    { 作者:零雨其蒙

    創(chuàng)建時間:2007-5-24

    Blog:blog.csdn.net/sslaowan

        m.tkk7.com/sslaowan

    }

     

    function TRetire.retireAwoke(qry:TOraQuery): TObjectList;

    var employeeRetired:TEmployeeRetired;

        paraList,employeesRetired:TObjectList;

        retirePara:TRetirePara;

        strSQL,strBirthdayUp, strBirthdayDown:string;

    begin

        _qry:= qry;

        employeesRetired:=TObjectList.Create;

        paraList:=getretireAwokeParaList;

        retirePara:=TRetirePara(paraList.Items[0]);  

        //滿足條件的男管理人員

     dtBirthday:=EncodeDate(Yearof(date)-StrToInt(retirePara._retireage),monthof(date),DayOf(date));

           strBirthdayDown:=formatdatetime('yyyy-mm-dd',dtBirthday);

           strBirthdayUp:=formatdatetime('yyyy-mm-dd',dtBirthday+strtoint(retirePara._uptime));

     _qry.Close;

     _qry.SQL.Clear;

     

     strSQL:='select employeeid,employeename,dptname,birthday,sex,EMPLOYEETYPE';

     strSQL:=strSQL +' from HR1_EMPLOYEE left join hr1_workdept on hr1_employee.workdeptid=hr1_workdept.dptid ';

     strSQL:=strSQL +' where sex=:sex';

      strSQL:=strSQL +' and EMPLOYEETYPE=:EMPLOYEETYPE and BIRTHDAY between '+''''+strBirthdayDown+''''+' and '+''''+strBirthdayUp+''''; strSQL:=strSQL+'order by dptid asc';

     _qry.SQL.Text :=strSQL;

     _qry.ParamByName('EMPLOYEETYPE').AsString :=retirePara._emp_type;

     _qry.ParamByName('SEX').AsString :=retirePara._sex;

     _qry.Open;

     

     if _qry.RecordCount>0 then

     begin

        while not _qry.Eof do

        begin

           employeeRetired:=TEmployeeRetired.Create;

           employeeRetired._ID:=_qry.FieldByName('employeeid').AsString;

           //以下省略若干代碼

           employeesRetired.Add(employeeRetired);

        end;

     end;

     retireAwoke:=employeesRetired;

    end;

    很高興,測試通過了!不過實話實說,也并非一次就能做成功的,其中SQL語句寫錯了,就查了半天,DUnit報錯說missing expression。我就是一個這樣馬虎的人,很難把程序一下子寫對,有DUnit做保證,小步前進,以免陷入絕境,在Bug叢生的密林中尋找,當是非常痛苦的事情了。

    3.5 重構(gòu)

    對于XP,我覺得簡單設(shè)計、TDD、重構(gòu)、持續(xù)集成、小規(guī)模發(fā)布是連在一起的實踐。簡單設(shè)計而沒有重構(gòu),就變成了Code and Fix。只有重構(gòu),而沒有單元測試的保證,無異于徒手穿越原始森林,沒有安全保證。單元測試,繼而持續(xù)集成、小規(guī)模發(fā)布,才能實現(xiàn)工作的軟件。再加之結(jié)對編程,以提高代碼質(zhì)量;完全客戶現(xiàn)場,以捕獲最真實的需求和得到最真實的反饋,以最快速最真實的態(tài)度響應(yīng)變化;集體代碼所有制,以減少人員流動的風(fēng)險,和提高復(fù)用;40小時工作日,以避免累死和創(chuàng)建更優(yōu)質(zhì)的代碼。這是我體會到的XP實踐的好處,隨著實踐和思考的增多,我覺得自己越來越認同XP的觀點了。

    閑言少敘,繼續(xù)編程。繼續(xù)完成其余三種情況,目前而言,就只有四種類型的員工。

     |      管理人員  |      工人

    |                               |

    |                               |

    從注釋(//滿足條件的男管理人員)開始,下面每一類型都要重復(fù)一次代碼,當然,可以直接在其中寫循環(huán)語句,但是看到Too Long Method恐懼癥,大量的查詢代碼混在這個方法,讓我覺得很別扭,于是我覺得將它們重構(gòu)出來,采用Extract Method

    首先新建getretireAwokeList方法,將從數(shù)據(jù)庫中取出需要被提醒的退休人員的代碼Extract到其中。然后整理局部變量。

    不斷的編譯,來幫助我檢查錯誤,是不是某些局部變量沒有遷移過來,有沒有變量重名等。經(jīng)過一番折騰,編譯通過,運行測試。

    進行測試,悲劇發(fā)生了,在顯示綠條后死機了,我想可能是內(nèi)存釋放有問題了。

    結(jié)果果然如此,下面這段程序結(jié)束后沒有釋放employeeRetiredList

    { 作者:零雨其蒙

    創(chuàng)建時間:2007-5-24

    Blog:blog.csdn.net/sslaowan

        m.tkk7.com/sslaowan

    }

    procedure TTestRetire.testRetireAwoke;

    var employeeRetiredList:TObjectList;

            i:integer;

    begin

          employeeRetiredList:=TObjectList.Create;

           _employeeRetired:=TEmployeeRetired(employeeRetiredList.Items[0]);

          CheckEquals(' YG000046',_employeeRetired._ID);

    CheckEquals('李隆基',_employeeRetired._Name);

    employeeRetiredList.Free;

    end;

    釋放了employeeRetiredList之后,其中的所有對象也就跟著被釋放了。

    3.6 整合UI

    最后一步,設(shè)計一個展示被提醒的退休人員信息的Form,然后放置一個叫做sgdRetireStringGrid,這時就是通過列表循環(huán)賦值到StringGird中就可以了。

    { 作者:零雨其蒙

    創(chuàng)建時間:2007-5-24

    Blog:blog.csdn.net/sslaowan

        m.tkk7.com/sslaowan

    }

     

    procedure TForm1.showEmployeeRetired;

    var employeeRetiredList:TObjectList;

        i:integer;

        _employeeRetired:TEmployeeRetired;

         retire:TRetire;

    begin

       sgdRetire.Cells[0,0]:='員工編號';

       sgdRetire.Cells[1,0]:='姓名';

       sgdRetire.Cells[2,0]:='部門';

       sgdRetire.Cells[3,0]:='生日';

       sgdRetire.Cells[4,0]:='性別';

       sgdRetire.Cells[5,0]:='工種';

       sgdRetire.Cells[6,0]:='距離退休天數(shù)';

     

       retire:=TRetire.Create;

       employeeRetiredList:=retire.retireAwoke(qryRetire);

       sgdRetire.RowCount:= employeeRetiredList.Count+1;

       for i:=0 to employeeRetiredList.Count-1 do

       begin

         _employeeRetired:=TEmployeeRetired(employeeRetiredList.Items[i]);

         sgdRetire.Cells[0,i+1]:=_employeeRetired._ID;

         sgdRetire.Cells[1,i+1]:=_employeeRetired._Name;

         sgdRetire.Cells[2,i+1]:=_employeeRetired._Dept;

         sgdRetire.Cells[3,i+1]:=_employeeRetired._Birthday;

         sgdRetire.Cells[4,i+1]:=_employeeRetired._Sex;

         sgdRetire.Cells[5,i+1]:=_employeeRetired._WorkType;

         sgdRetire.Cells[6,i+1]:=_employeeRetired._DaysLeft;

       end;

       employeeRetiredList.Free;

       retire.Free;

    end;

    但是一定要注意:對象的釋放問題,對象生命周期的開始到完結(jié),一定要好好想清楚。不知道那些癡迷與C/C++的開發(fā)者,是如何處理內(nèi)存釋放問題的,我的Delphi面向?qū)ο缶幊探?jīng)驗還不算多,對我而言,仔細分析每個對象內(nèi)存是否被釋放了,真的是一件非常痛苦的事情。所以還是比較喜歡Java那樣帶垃圾回收器的語言。但是通過創(chuàng)建和釋放對象,來理解對象的生命周期意義,對于理解對象還是很有幫助的。

    4       真的要選擇TDD嗎?

    仔細的設(shè)計測試用例,不僅是在思考對象具有哪些責任,同時也是在思考對象如何使用。先總結(jié)一下使用TDD的幾點好處:

    1.         可以不斷地測試,以保證代碼是正確的。而且在正在開發(fā)的這一段時間內(nèi),測試是可以不斷進行的,期望的結(jié)果不會有什么大的變動。(時間長了就不好說了,比如本文給出的例子。)

    2.         由于有了可以重復(fù)進行的測試保障,因此可以大膽的進行重構(gòu)了。

    3.         在編寫產(chǎn)品代碼之前考慮什么情況是正確的,而且可以編寫代碼邊增加測試數(shù)據(jù),這樣可以有利于全面的測試。據(jù)說配合測試裝置,還可以由業(yè)務(wù)人員填入測試數(shù)據(jù),這樣樣本就更大了。

    4.         因此結(jié)果是騙不了人的,當紅條亮起時,你就知道是剛剛編寫的那段代碼錯了。

    5.         在編寫測試用例時,就是在思考這個對象干什么的時候,這有利于養(yǎng)成針對接口編程的好習(xí)慣,同時這樣也就自然的為對象分配了職責。

    6.         在編寫測試用例時,還需要考慮這個對象是如何使用的,這無疑就是在編寫對象的使用說明書了。

    7.         由于以上兩點,可以使我們?yōu)榱耸箤ο蠡驅(qū)ο蟮姆椒ū阌跍y試,而降低了對象間的耦合,減少了依賴。

    8.         進行測試驅(qū)動開發(fā),還會使得我們傾向于領(lǐng)域驅(qū)動開發(fā),而不是UI驅(qū)動或數(shù)據(jù)庫驅(qū)動,同時這也有利于將各層解耦。

    另外有以下幾點值得思考:

    1.         測試驅(qū)動開發(fā)提高總體效率的前提是什么?毫無疑問,從長遠來看,測試驅(qū)動開發(fā)提高了代碼質(zhì)量,而軟件的成本往往從維護階段開始(譬如我們現(xiàn)在正在維護的這個項目,讓人欲死欲活的)。但是,我在進行TDD實踐時,確實比直接開發(fā)花費了更多時間,包括思考測試用例應(yīng)該怎樣寫,比如測試持久層就想了半個多小時。

    2.         另外,真的要關(guān)掉“異常時中斷”功能嗎?有幾次總是報ORA***:missing expression錯誤,我真的想設(shè)個斷點看看到底SQL語句成什么樣了。雖然錯誤的發(fā)生就在那兩三行中,或許某一個不超過20行的函數(shù)中,但是我還是花費了很多時間去反復(fù)測試,仔細看代碼,觀察到底在哪錯了,因為DUnit不會告訴你是哪行錯了。(或許有告訴,我不知道在哪而已)

    3.         不知道為什么,我是將產(chǎn)品代碼所在的文件夾放在了search path中了,可是總會遇到產(chǎn)品代碼更新了,測試代碼那邊讀到的還不是最新結(jié)果。剛開始,我寫到會出現(xiàn)那個代碼已更改的提醒,之后代碼就同步了,可是后來重啟了一次Delphi就不行了。

    4.         就是內(nèi)存釋放問題,在測試代碼中和產(chǎn)品代碼中都要考慮內(nèi)存釋放問題,很煩。

    雖然在進行TDD實踐過程中,碰到了不少挫折,不過總體而言,我覺得驅(qū)動測試開發(fā)讓我在編寫測試用例時思考對象的工作方式,是一種漸進的思考過程。其實,我一直的編程習(xí)慣是,面對一個問題先花費一兩天時間思考,把各種關(guān)系都搞清楚了,然后在兩個小時內(nèi)一氣呵成,由于思考的很清楚,因此錯誤也挺少的。Planned Design和進化式設(shè)計兩種方式都讓我感到獲益,XP之所以叫做極限編程,其中一個極限的部分可能就是其簡單設(shè)計的極限,不需要任何的架構(gòu)設(shè)計,就根據(jù)用戶故事開始編程。不過我覺得我需要更多的實踐TDD,那些編寫測試用例遇到的困難,我想或許每個新手都會遇到,唯有多多實踐才能真正的提高。

    5       大項目的思考

    在大項目和大的團隊中推行TDD是有困難的。

    1.         首先,TDD需要編寫產(chǎn)品代碼以外的測試代碼,很多程序員為了快速完成任務(wù)(有些人的時間只夠編寫產(chǎn)品代碼),不會愿意寫測試代碼的。雖然它從長遠考慮會有很多好處,但是現(xiàn)在的程序員有多少會想很遠呢(這也跟責任心有關(guān))?可能等到維護時,我都走人了。

    2.         其次,TDD需要開發(fā)人員有很強的設(shè)計能力,在這里我討論OO設(shè)計,不過我發(fā)現(xiàn),在中國,程序員對OO都知之甚少。更甭說進行優(yōu)秀的OO設(shè)計了。況且,Delphi不支持垃圾回收,習(xí)慣于“拖拉機”方式的程序員估計要造成很多混亂了。

    3.         最后,比如我們這樣的大型項目,數(shù)百張數(shù)據(jù)表,上千個窗體,如何進行TDD,如果我自己沒做過,真是很難說服老板推薦這么干。雖然我堅信,這是可行的。

    或許在您的項目中,已經(jīng)成功地應(yīng)用了TDD,那么希望您能夠分享您的經(jīng)驗。如果您經(jīng)歷的大型項目正在使用TDD,那么我們所有的讀者都將非常感興趣。

     

    希望本文有任何不足之處,都請與我聯(lián)系,在我的Blog上留言或給我發(fā)郵件:sslaowan@gmail.com

     

    posted on 2007-05-24 22:31 零雨其蒙 閱讀(1323) 評論(1)  編輯  收藏 所屬分類: 面向?qū)ο罄碚撆c實踐

    評論

    # re: 零雨其蒙:Practicing Test-Driven Development by Example Using Delphi  回復(fù)  更多評論   

    精神可嘉。還需要去理解TDD(AGILE)的內(nèi)涵和本質(zhì)。讀了您的代碼,覺得你的理論和實踐還是有不小的差距(恕我直言),有為了測試而強行測試(可能表達得不準確)的感覺。
    主站蜘蛛池模板: 青青操免费在线观看| 最近免费中文字幕MV在线视频3| 国产免费牲交视频| 国产V片在线播放免费无码| 日韩亚洲一区二区三区| 51在线视频免费观看视频| 久久综合亚洲色hezyo| 在线亚洲午夜理论AV大片| 真人做A免费观看| 四虎影视久久久免费| 亚洲综合色丁香麻豆| 免费欧洲美女牲交视频| 色欲A∨无码蜜臀AV免费播| 男人的天堂av亚洲一区2区| 亚洲成A人片在线观看无码不卡 | 最新亚洲春色Av无码专区| 亚洲阿v天堂在线2017免费| 美女内射毛片在线看免费人动物| 色婷婷精品免费视频| 亚洲精品自在线拍| 亚洲国产成人精品女人久久久 | 亚洲日本天堂在线| 亚洲熟妇无码另类久久久| 成年女性特黄午夜视频免费看 | 国产亚洲美女精品久久久| 成人免费视频77777| 少妇性饥渴无码A区免费| 亚洲欧美日韩中文字幕一区二区三区| 精品久久久久久亚洲| 日韩一品在线播放视频一品免费| 久久国产精品萌白酱免费| 九九九精品视频免费| 亚洲爆乳AAA无码专区| 亚洲女人18毛片水真多| 亚洲人成人无码网www电影首页| 国产免费观看a大片的网站| 女性无套免费网站在线看| 91精品国产免费久久久久久青草| 暖暖日本免费中文字幕| 亚洲精品黄色视频在线观看免费资源| 亚洲国产精品ⅴa在线观看|