<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      享受代碼,享受人生

      SOA is an integration solution. SOA is message oriented first.
      The Key character of SOA is loosely coupled. SOA is enriched
      by creating composite apps.
        博客園  :: 首頁  :: 新隨筆  :: 聯系 :: 訂閱 訂閱  :: 管理

      Enterprise Test Driven Develop

      Posted on 2005-08-09 10:09  idior  閱讀(6928)  評論(21)    收藏  舉報
                                 Enterprise Test Driven Develop


             TDD
      這個概念出現至少也有兩年多了, 在大家剛接觸它時候, 幾乎沒人不拍手鼓掌, 測試驅動的概念確實可以為我們帶來很多的好處. 泡泡在前不久寫了一篇很不錯的TDD的文章, 可是我還是忍不住在他的評論中說到這篇文章有點”. 確實,同樣的概念在書中, 在大家的口中已經被說爛了. 但是TDD真正給我們帶來什么呢? 有多少人在用TDD? 為什么這么好的技術到了應用中就被人們拋棄了呢?

      (: Design Pattern面世都十幾年了, 現在還是有不少好書在介紹它,所以不代表不好,泡泡的總結在園子里還是最好的一篇TDD文章, 特別建議剛接觸TDD的參考之.) 

       What’s Wrong with Old TDD 

             TDD的應用是阻撓TDD發展的重要原因, 回想一下有關TDD書中舉的那些例子. 再想想你學習了解TDD自己又做了哪些應用? --- 類庫, 最基本的幾個類之間協作, 不涉及數據庫,不涉及UI,不涉及企業服務. 這樣看來TDD最適用的場合就是不涉及復雜應用的類庫. 比如一個保齡球游戲, 一個Money兌換系統. 如果是這樣那TDD自然沒有人用了, 真正的項目有幾個不涉及復雜應用呢, 有幾個不和數據庫,界面打交道? 可是一旦你想將TDD應用于此時, 你就會發現煩不勝煩, 無從下手.

             難道TDD真得這么不堪一擊? 先來看一個小例子.

             在我們的一個應用中需要將事情的處理結果用郵件發送到相應的客戶, 企業應用中很常見的一個例子. 這時我們需要使用到郵件服務器了, 算是一個稍微復雜的應用.

          3     public class BusinessReporter

          4     {

          5         public IEmailService emailService; // an external object.

          6 

          7         public BusinessReporter(IEmailService email)

          8         {

          9             this.emailService = email;

         10         }

         11 

         12         // method under test.

         13         public void Report()

         14         {

         15             //do something real

         16             string to="idior";  //just for test

         17             string message="hello";

         18             emailService.Send(message,to);

         19         }

         20     }

       

      就這樣一段簡單的代碼, 你如何編寫測試代碼? 如果你想真實的測試郵件到底有沒有發出去, 你甚至得專門為此寫一個讀取郵箱內容的組件.

         19      [Test]

         20         public void TestReport()

         21         {

         22             EmailService emailService=new EmailService();

         23             BusinessReporter bizReporter = new BusinessReporter(emailService);

         24             bizReporter.Report();

         25             EmailChecker emailChecker=new EmailChecker();

         26             Assert.Equals("hello",emailChecker.GetEmail("idior"));

         27         }

      看看你要做什么, 在實現這個Report功能前,你要實現EmailService, 并且還要專門為測試去做一個EmailChecker這個類.但是別忘了你的目的僅僅是測試BusinessReporter類下 Report的方法. 這里是單元測試不是集成測試, 如果為了測試一個功能而牽涉到很多的對象是不利于隔離錯誤的. 但是不這樣做,又如何測試Report方法是否正確呢?

      Introduce Mock Object

      從這個例子可以看出僅僅利用NUnitTDD是無法很好的完成單元測試的任務. 甚至根本無法進行實際的TDD開發. 試想一下我在測試我的業務邏輯的時候, 其中調用了Persistence Logic, 比如在Domian Object中使用了DAL. 這個時候我怎么測試我的業務邏輯, 難道必須先寫好DAL再來做嗎? 難道我要把被測對象所依賴的對象都實現了,才能完成我的被測對象? 因此光靠NUnit是很難勝任那種需要多對象協作的業務的開發的. 或許你已經知道我要引出誰了. 沒錯 Mock Object. 這個遠沒有NUint為人熟悉, 但是在TDD實際開發中絕對重要的角色. 可以說沒有它(Mock)你根本無法在TDD下完成實際中的企業開發.

      讓我們看看使用了Mock對象后,上面那個BusinessReporter又是如何測試的.

          1         [Test]

          2         public void TestReport()

          3         {

          4             using (MockRepository mocks = new MockRepository())

          5             {

          6                 //set up

          7                 IEmailService emailSvcMock= mocks.CreateMock(typeof(IEmailService)) as IEmailService;

          8                 BusinessReporter bizReporter = new BusinessReporter(emailSvcMock);

          9 

         10                 //what we expect to happen

         11                 emailSvcMock.Send("hello", "idior");

         12 

         13                 //end record

         14                 mocks.ReplayAll();

         15 

         16                 //execute the test method

         17                 bizReporter.Report();

         18             }//verify when mocks dispose

         19         }

         20    //: 本文采用了Rhino Mocks2作為Mock框架,詳見Rhino Mocks2介紹      

          怎么樣比上面的方法簡單了許多吧.
         
      既然說到了Enterprise Test Driven, 就不能不提有關Data Access Logic的測試, 下面就來看一個例子. 這是一個類似電子購物的系統. 你有一個購物籃, 籃子中裝有一些Item, 每個Item對應了一種商品以及它的數量. 當我們設定購買一類商品時, 就創建一個Item, 并為該Item賦上它的商品ID和數量, 在設定ID的同時,我們從數據庫中獲得該商品的價格以及名稱(見Class BasketItem L19-24黑體字). 就是這么一個小例子, 可以看到在設定ID的時候我們使用到了DAL.下面來看看具體的代碼:

          1        [Test]

          2         public void BasketStub()

          3         {

          4             using (MockRepository mocks = new MockRepository())

          5             {

          6                 IShoppingDataAccess dataAccess = mocks.CreateMock(typeof(IShoppingDataAccess)) as IShoppingDataAccess;

          7                 Basket b = new Basket(dataAccess);

          8 

          9                  SetupResult.On(dataAccess).Call(dataAccess.GetUnitPrice(1)).Return(new decimal(99));

         10                 SetupResult.On(dataAccess).Call(dataAccess.GetProductName(1)).Return("The Moon");

         11                 SetupResult.On(dataAccess).Call(dataAccess.GetUnitPrice(5)).Return(new decimal(47));

         12                 SetupResult.On(dataAccess).Call(dataAccess.GetProductName(5)).Return("Love");

         13 

         14                 mocks.ReplayAll();

         15 

         16                 b.AddItem(new BasketItem(1, 2, dataAccess));

         17                 b.AddItem(new BasketItem(5, 1, dataAccess));

         18 

         19                 Assert.AreEqual(99*2+1*47, b.CalculateSubTotal());

         20             }

         21         }

          1 public class Basket

          2 {

          3     private ArrayList basketItems;

          4     private Guid basketID;

          5     private IShoppingDataAccess dataAccess;

          6     public Basket(IShoppingDataAccess dataAccess)

          7     {

          8         Initialize(dataAccess);

          9     }

         10     public void AddItem(BasketItem item)

         11     {

         12         basketItems.Add(item);

         13     }

         14     public void Save()

         15     {

         16         dataAccess.SaveBasketItems(basketID, (BasketItem[])basketItems.ToArray(typeof(BasketItem)));

         17     }

         18     public decimal CalculateSubTotal()

         19     {

         20         decimal subTotal = 0;

         21         foreach (BasketItem item in basketItems)

         22         {

         23             subTotal += item.GetPrice();

         24         }

         25         return subTotal;

         26     }

         27     private void Initialize(IShoppingDataAccess dataAccess)

         28     {

         29         this.dataAccess = dataAccess;

         30         basketItems = new ArrayList();

         31         basketID = Guid.NewGuid();

         32     }

         33 }

          1 public class BasketItem

          2 {

          3     private decimal unitPrice;

          4     private int productID;

          5     private int quantity;

          6     private IShoppingDataAccess dataAccess = null;

          7     private string productName;

          8 

          9     public BasketItem(int productID, int quantity, IShoppingDataAccess dataAccess)

         10     {

         11         Initialize(productID, quantity, dataAccess);

         12     }

         13 

         14     public decimal UnitPrice { get { return unitPrice; } }

         15 

         16     public int ProductID

         17     {

         18         get { return productID; }

         19         set   //when the productID being setted, we get the price and name from database synchronized

         20         {

         21             productID = value;

         22             unitPrice = dataAccess.GetUnitPrice(productID);

         23             productName = dataAccess.GetProductName(productID);

         24         }

         25     }

         26 

         27     public int Quantity

         28     {

         29         get { return quantity; }

         30         set { quantity = value; }

         31     }

         32 

         33     public string ProductName { get { return productName; } }

         34 

         35     public decimal GetPrice()

         36     {

         37         return unitPrice * quantity;

         38     }

         39 

         40     private void Initialize(int productID, int quantity, IShoppingDataAccess dataAccess)

         41     {

         42         this.dataAccess = dataAccess;

         43         ProductID = productID;

         44         Quantity = quantity;

         45     }

         46 }

             記得以前看<<Test-Driven Development in Microsoft .NET>>這本書的時候對開篇的DAL測試很不感冒.那本書就丟之一旁. 如果用傳統的方法來測試DAL, 你會發現不僅麻煩而且丑陋.

      細心的讀者可能注意到了這里測試的方法名是BasketStub 而不是BasketMock. 可能你以前也聽說過Martin FowlerMock Arent Stub. 那么什么是Stub 什么是Mock? 讓我們從面向對象一個最重要的原則說起.

      Don’t Ask, Tell

      先舉一個計算工資的老例子. 不同的員工有各種不同的工資計算方法, 這種情況下如何為全公司的員工計算呢? 想想現實中的情況, 在公司可能有一個會計部,每到發薪的日子. 會計部就會把全公司的員工資料集中到一起, 看看張三是什么類型的員工, 如果是小時工就按小時工來計算,如果是辦公室主任就按主任的方法來計算. 基于這樣的考慮, 我們在建模的時候也會相應的建立一個類似于會計部的類, 其中定義了一個計算工資的方法, 在方法中每計算一個員工前先會詢問一個這個員工的類型, 如果是A類型怎么算, 如果是B類型怎么算, 可以想象這個計算工資的方法將十分龐大,并充滿了if else(switch case)的代碼.將來如果多了一個員工類型,還要來修改這個方法.

      Ask員工類型, 根據類型選擇相應的處理邏輯, 這就是以上的方案,可以看出這不是一個好的解決辦法. 好的方法是什么呢? 既然我們要根據員工的類型來判斷采用何種計算方法, 而員工顯然知道自己是何種類型以及自己的工作量, 那么為什么不交給員工自己來算呢?(具體實現的時候就會涉及到多態等等方法, 這不是本文討論的重點,在此不做詳細介紹)此時會計部干什么呢? 計算工資這個活動總要有人發起, 所以會計部現在的工作就是Tell所有的員工, 讓大家計算工資,然后把結果匯總.

      現在你可以看到Don’t Ask, Tell的影子了吧.

      Ask 帶來的壞處:

      1.         破壞對象的封裝性, 你不得不暴露很多的屬性供別人Ask.

      2.         容易使得一些對象過于復雜(會計部), 而一些對象(員工)又過于簡單, 甚至成為了僅僅包含數據的對象.

      Tell 帶來的好處:

      1.    更多的考慮責任分配的合理性, 方法涉及的數據在哪里方法就應該在哪里. 這樣對象的內聚性就大大加強了.

      2.    增強了對象的封裝性, 對象不必暴露更多的屬性.

      3.    可以充分發揮面向對象的特性,比如多態, 減少對象從而獲得更強的可維護性,擴展性以及面對變化的能力.

      上面那個計算工資的例子可以說是被用濫了, 再來看看在其他場合如何運用Don’t Ask, Tell的思想. 集合的遍歷操作, 在日常的編程中屢見不鮮. 你是否考慮過它也在一定程度上破壞了Don’t Ask, Tell原則呢? We ask for every element in Collection, then operator on it. Why not just tell the collection to do something. 如果采用ask的辦法, 在我們的程序中將不斷的出現遍歷集合的操作. (重復代碼,Bad Smell) 所以我們應該將盡量將集合遍歷的操作放在集合內即Refactory away External Loops. 如果你留意了.Net2.0對集合類的最新支持ForEach,你就會發現MS也考慮到了這點.

          1     class Program

          2     {

          3         static void Main(string[] args)

          4         {

          5             List<string> strs = new List<string>();

          6             strs.Add("hello");

          7             strs.Add("world");

          8 

          9             strs.ForEach( Console.WriteLine);           

         10         }      

         11    }

      不過如我在.Net2.0的集合操作 --- What i hope? 一文中提到, 似乎考慮的還不是非常完善.

            當然事情沒有絕對, 有人會問究竟Tell到什么程度呢? 是不是Money還要負責自己的兌換操作? 這就看你把匯率放哪了. 如果匯率也在Money, Money可以提供兌換的功能, 但是可以看出這不是一個好的設計. 匯率或許應該在Bank對象中, Money的兌換操作還是放在Bank中比較合適. 這里就涉及到責任分配的問題, 責任分配的好壞將決定對象的內聚性及耦合性. 其關鍵在于責任(方法)所涉及的主要狀態(數據)到底在哪個對象當中.

            Don’t Ask, Tell可以說是面向對象中一個非常重要的原則(Martin Fowler甚至將其稱為面向對象最難理解的一個原則之一), 如果將其引申到TDD中又帶來什么樣的新觀點呢?

      State Based Test vs. Interaction Based Test

      先回想一下以往使用NUnit的時候我們是如何測試. We ask for some state of an object, then we make some assertion on it. Why not just tell what we want to happen? 這里將要發生的事也就是我們在Expectation階段定義好的那些方法和方法中的參數及返回值..

      由此可見, xUnitxMock, 不僅僅是兩個工具這么簡單, 它們分別代表了兩種不同的測試思想, 是基于狀態的測試還是基于行為的測試?

      記得上面所說的Ask帶來的壞處嗎? 它也會影響到基于狀態測試而開發出來的系統. 為了使用Assert測試對象, 你不得不暴露出很多的屬性, 甚至于這些屬性在正常的應用并不被使用, 但是為了測試,你不得不暴露它. 破壞了對象的封裝性. 使用基于狀態的測試由于較少的考慮對象的行為, 自然會導致其驅動開發出來的對象缺少行為,以至成為數據.

      如果你遵循"Tell, Don't Ask"準則的話, 由于對象的封裝性更好了, 將沒有太多的屬性暴露出來供你Assert. 在編程的時候, 我們應該更多的關注于程序是如何做的而不是關注于程序內部的狀態變化

         
      在面向過程的方法中
      , 模塊之間通過讀寫一個共享數據結構來進行交互. 這時我們可以通過對共享數據的狀態變化進行測試來保證程序的正確性. 但是在面向對象的方法中, 功能是通過對象之間相互發送消息來實現, 因此這個相互發送消息的過程應該被測試. 因此在面向對象的測試中應該更多的基于行為來進行測試.

      面向對象的程序中由于引入了對象的概念, 每個對象負責其自身所涉及的責任, 因此一個功能通常是由多個對象協作而完成, 此時如果采用基于狀態的測試方法你將很難進行測試, 比如文章一開始提到的那個使用EmailService的例子. 當一個模塊被分為多個對象的時候, 重點在于對象間如何交互而不是每個對象的內部狀態. 因此對于那種需要多對象協作的功能使用基于行為的測試方法將更加合理,也更加方便進行測試. 并且在使用Mock對象的時候你會不自覺的使用接口而不是具體類(Mock框架中通常是為接口創建Mock對象,而不是對具體類),從而降低對象之間的依賴程度. 這個過程也就是GOF所說的Design to interface.

      當你使用Interaction Based Test的時候, 一旦你發現了錯誤,那么基本上就是被測試的對象出了問題。而在State Based Test中由于依賴對象采用的是實際的對象,產生錯誤的可能性非常大, 因此錯誤的來源可能是測試對象所依賴的其他對象而非測試對象本身。實際上State Based Test已經不僅僅是單元測試,它在某些場合已經有了集成測試的味道。而使用Interaction Based Test,則更接近于單元測試,這樣有它的優點---隔離了錯誤的發生,更容易找到錯誤,但是卻也失去了集成測試所帶來的保障,很有可能你的單元測試全部通過了,但是實際上在其中隱藏了一些集成時會發生的Bug。但是作為程序員的測試, 個人還是偏向于單元測試.

             Interaction Based Test還會給系統的設計帶來好處,當你在為主對象編寫測試代碼的時候,你考慮到了它與其他(依賴)對象的交互, 這時你就會為其他對象設計好一些行為規范(Interface),當你完成主對象的測試時, 依賴對象的影子也出來了。

      Mock Arent Stub

      現在讓我們回到先前那個數據庫的例子. 那里的測試方法名是BasketStub, 顯然使用的是Stub. 它們的區別是什么? 不如先來看看如果使用Mock那么測試方法又將是什么樣. 

          1      [Test]

          2         public void BasketMock()

          3         {

          4             using (MockRepository mocks = new MockRepository())

          5             {

          6                 IShoppingDataAccess dataAccess = mocks.CreateMock(typeof(IShoppingDataAccess)) as IShoppingDataAccess;

          7                 Basket b = new Basket(dataAccess);

          8 

          9                  Expect.On(dataAccess).Call(dataAccess.GetUnitPrice(1)).Return(new decimal(99));

         10                 Expect.On(dataAccess).Call(dataAccess.GetProductName(1)).Return("The Moon");

         11                 Expect.On(dataAccess).Call(dataAccess.GetUnitPrice(5)).Return(new decimal(47));

         12                 Expect.On(dataAccess).Call(dataAccess.GetProductName(5)).Return("Love");

         13 

         14                 mocks.ReplayAll();

         15 

         16                 b.AddItem(new BasketItem(1, 2, dataAccess));

         17                 b.AddItem(new BasketItem(5, 1, dataAccess));

         18                 Assert.AreEqual(99*2+1*47, b.CalculateSubTotal());

         19             }

      20                 }

      再把上面那段Stub的測試代碼放在這對比一下.

         1        [Test]

          2         public void BasketStub()

          3         {

          4             using (MockRepository mocks = new MockRepository())

          5             {

          6                 IShoppingDataAccess dataAccess = mocks.CreateMock(typeof(IShoppingDataAccess)) as IShoppingDataAccess;

          7                 Basket b = new Basket(dataAccess);

          8 

          9                  SetupResult.On(dataAccess).Call(dataAccess.GetUnitPrice(1)).Return(new decimal(99));

         10                 SetupResult.On(dataAccess).Call(dataAccess.GetProductName(1)).Return("The Moon");

         11                 SetupResult.On(dataAccess).Call(dataAccess.GetUnitPrice(5)).Return(new decimal(47));

         12                 SetupResult.On(dataAccess).Call(dataAccess.GetProductName(5)).Return("Love");

         13 

         14                 mocks.ReplayAll();

         15 

         16                 b.AddItem(new BasketItem(1, 2, dataAccess));

         17                 b.AddItem(new BasketItem(5, 1, dataAccess));

         18                 Assert.AreEqual(99*2+1*47, b.CalculateSubTotal());

         19              }

         20         }

      不同之處僅僅在于Line9-12. 它們的執行效果也完全一樣, 不同之處在于如果你把StubL17-18注釋掉, 測試依然通過, 而把MockL17-18注釋掉, 測試失敗. 為什么? 因為Mock注重的是對象的行為而不是狀態. 你在L11-12已經聲明將會調用到dataAccess.GetUnitPrice(5).但是由于你的注釋, 使得該行為沒有發生, 自然應該報錯. 而在Stub,你僅僅聲明當調用到dataAccess.GetUnitPrice(5)返回10,并沒有表示該行為一定要發生, 所以即使L17被注釋掉, 測試依然通過..

      由此看來Stub Mock雖然都是模擬對象, 甚至它們的創建方法都一摸一樣, 但是它們的用法卻不一樣, 它們代表的測試模式也完全不同, Stub是模擬對象在State Base Test下的代表,而Mock則是模擬對象在Interaction Base Test下的代表.

      通常會把那種不關心調用過程和參數,而僅僅返回假數據的模擬對象叫做Stub. 它被用于創建那些需要被模擬的代價昂貴的對象, 最常見的例子就是模擬數據庫連接, 如果在測試中真正去連接一個數據并從中獲得數據結果是很麻煩的一件事,而且在測試中不利于隔離錯誤的發生,因為數據庫訪問出錯的原因太多了. 所以我們經常創建一個Stub對象輸出假數據用于模擬從數據庫獲得我們期望的數據. 而Mock則更多的關注于Mock 對象的某個方法是否被調用(行為是否發生), 以及調用時的參數和返回值.

      我們之所以需要Stub或者Mock是因為我們將要測試的對象依賴于其他的對象.如果是State Based Test, 我們只有在依賴對象很難使用真實對象的時候(比如我們還未實現依賴對象)才會使用模擬對象(Stub)來返回一些假數據供我們測試. Interaction Based Test卻毫不忌諱對模擬對象(Mock)的使用, 即使我們已經實現了依賴對象依然會使用Mock. 因為Interaction Based Test關注的是對象行為的發生(方法的調用)以及發生時的參數和返回值,而這只有依賴于優秀的Mock框架我們才能方便的測試, 使用了真實對象反而不利于測試.
                                                 
                                        

                                
      NoteStub 不到萬不得以不用, Mock 能用則用.

       

       


      Other Words

      不要去測試類中的每個方法. 要測試這個類對外所能提供的功能, 這些功能可能是其中的幾個重要方法,可能需要類中的幾個方法協作. 記住一點, TDD中測試代碼也是文檔的一部分, 你應該通過你的測試代碼告訴別人如何使用這個類.

      Resources:

      Don’t Ask, Tell

      Rhino Mocks 2

      Mock Arent Stub

      MSDN Magazine  October 2004  Unit Testing: Mock Objects to the Rescue! Test Your .NET Code with

      NMock

      AssertThat --- 測試的重用

      注:本文編寫時間比較早,現在的RhinoMock已經發生了一些大的變化,詳細內容參考RhinoMock2

      。不過主要問題出在示例代碼上,本文所述之思想依然有效。

      主站蜘蛛池模板: 午夜DY888国产精品影院| 人妻少妇88久久中文字幕| 加勒比无码人妻东京热| 精品国产免费人成在线观看 | 国产对白熟女受不了了| 久久影院综合精品| 少妇精品视频一码二码三| 在线午夜精品自拍小视频| 色综合久久久久综合体桃花网| 国产久免费热视频在线观看| 精品国产成人a在线观看| 久久国产精品成人影院| 太白县| 浴室人妻的情欲hd三级国产| 亚洲av成人三区国产精品| 在线日韩一区二区| 久久久久无码精品国产h动漫| 国产亚洲精品福利在线无卡一| 无码AV动漫精品一区二区免费| 极品白嫩少妇无套内谢| 免费特黄夫妻生活片| 无码人妻一区二区三区免费N鬼沢| 安达市| AV人摸人人人澡人人超碰| 嫩草成人AV影院在线观看| 石阡县| 在线天堂中文新版www| 国产精品久久久尹人香蕉| 综艺| 999福利激情视频| 色呦呦 国产精品| 欧美和黑人xxxx猛交视频| 高潮迭起av乳颜射后入| 亚洲人成网站18禁止无码| 国内精品免费久久久久电影院97| 欧美大胆老熟妇乱子伦视频| 亚洲AV毛片一区二区三区| 99精品国产综合久久久久五月天 | 亚洲熟妇在线视频观看| 中文成人无字幕乱码精品区| 国产精品日韩中文字幕熟女|