日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
測試驅(qū)動開發(fā)上的五大錯誤

我曾經(jīng)寫過很多的糟糕的單元測試程序。很多。但我堅(jiān)持著寫,現(xiàn)在我已經(jīng)喜歡上了些單元測試。我編寫單元測試的速度越來越快,當(dāng)開發(fā)完程序,我現(xiàn)在有更多的信心相信它們能按照設(shè)計(jì)的預(yù)期來運(yùn)行。我不希望我的程序里有bug,很多次,單元測試在很多***的小bug上挽救了我。如果我能這樣并帶來好處,我相信所有的人都應(yīng)該寫單元測試!

創(chuàng)新互聯(lián)憑借在網(wǎng)站建設(shè)、網(wǎng)站推廣領(lǐng)域領(lǐng)先的技術(shù)能力和多年的行業(yè)經(jīng)驗(yàn),為客戶提供超值的營銷型網(wǎng)站建設(shè)服務(wù),我們始終認(rèn)為:好的營銷型網(wǎng)站就是好的業(yè)務(wù)員。我們已成功為企業(yè)單位、個人等客戶提供了網(wǎng)站制作、網(wǎng)站建設(shè)服務(wù),以良好的商業(yè)信譽(yù),完善的服務(wù)及深厚的技術(shù)力量處于同行領(lǐng)先地位。

作為一個自由職業(yè)者,我經(jīng)常有機(jī)會能看到各種不同的公司內(nèi)部是如何做開發(fā)工作的,我經(jīng)常吃驚于如此多的公司仍然沒有使用測試驅(qū)動開發(fā)(TDD)。當(dāng)我問“為什么”,回答通常是歸咎于下面的一個或多個常見的錯誤做法,這些錯誤是我在實(shí)施驅(qū)動測試開發(fā)中經(jīng)常遇到的。這樣的錯誤很容易犯,我也是受害者。我曾合作過的很多公司因?yàn)檫@些錯誤做法而放棄了測試驅(qū)動開發(fā),他們會持有這樣一種觀點(diǎn):驅(qū)動測試開發(fā)“增加了不必要的代碼維護(hù)量”,或“把時間浪費(fèi)在寫測試上是不值得的”。

人們會很合理的推斷出這樣的結(jié)論:

寫了單元測試但沒有起到任何作用,那還不如不寫。

但根據(jù)我的經(jīng)驗(yàn),我可以很有信心的說:

單元測試能讓我的開發(fā)更有效率,讓我的代碼更有保障。

帶著這樣的認(rèn)識,下面讓我們看看一些我遇到過/犯過的最常見的在測試驅(qū)動開發(fā)中的錯誤做法,以及我從中學(xué)到的教訓(xùn)。

1、不使用模擬框架

我在驅(qū)動測試開發(fā)上學(xué)到***件事情就是應(yīng)該在獨(dú)立的環(huán)境中進(jìn)行測試。這意味著我們需要對測試中所需要的外部依賴條件進(jìn)行模擬,偽造,或者進(jìn)行短路,讓測試的過程不依賴外部條件。

假設(shè)我們要測試下面這個類中的GetByID方法:

 
 
 
 
  1. public class ProductService : IProductService  
  2. {  
  3.     private readonly IProductRepository _productRepository;  
  4.      
  5.     public ProductService(IProductRepository productRepository)  
  6.     {  
  7.         this._productRepository = productRepository;  
  8.     }  
  9.      
  10.     public Product GetByID(string id)  
  11.     {  
  12.         Product product =  _productRepository.GetByID(id);  
  13.      
  14.         if (product == null)  
  15.         {  
  16.             throw new ProductNotFoundException();  
  17.         }  
  18.      
  19.         return product;  
  20.     }  

為了讓測試能夠進(jìn)行,我們需要寫一個IProductRepository的臨時模擬代碼,這樣ProductService.GetByID就能在獨(dú)立的環(huán)境中運(yùn)行。模擬出的IProductRepository臨時接口應(yīng)該是下面這樣:

 
 
 
 
  1. [TestMethod]  
  2. public void GetProductWithValidIDReturnsProduct()  
  3. {  
  4.     // Arrange  
  5.     IProductRepository productRepository = new StubProductRepository();  
  6.     ProductService productService = new ProductService(productRepository);  
  7.      
  8.     // Act  
  9.     Product product = productService.GetByID("spr-product");  
  10.      
  11.     // Assert  
  12.     Assert.IsNotNull(product);  
  13. }  
  14.      
  15. public class StubProductRepository : IProductRepository  
  16. {  
  17.     public Product GetByID(string id)  
  18.     {  
  19.         return new Product()  
  20.         {  
  21.             ID = "spr-product",  
  22.             Name = "Nice Product" 
  23.         };  
  24.     }  
  25.      
  26.     public IEnumerable GetProducts()  
  27.     {  
  28.         throw new NotImplementedException();  
  29.     }  

現(xiàn)在讓我們用一個無效的產(chǎn)品ID來測試這個方法的報錯效果。

 
 
 
 
  1. [TestMethod]  
  2. public void GetProductWithInValidIDThrowsException()  
  3. {  
  4.     // Arrange  
  5.     IProductRepository productRepository = new StubNullProductRepository();  
  6.     ProductService productService = new ProductService(productRepository);  
  7.      
  8.     // Act & Assert  
  9.     Assert.Throws(() => productService.GetByID("invalid-id"));  
  10. }  
  11.      
  12. public class StubNullProductRepository : IProductRepository  
  13. {  
  14.     public Product GetByID(string id)  
  15.     {  
  16.         return null;  
  17.     }  
  18.      
  19.     public IEnumerable GetProducts()  
  20.     {  
  21.         throw new NotImplementedException();  
  22.     }  

在這個例子中,我們?yōu)槊總€測試都做了一個獨(dú)立的Repository。但我們也可在一個Repository上添加額外的邏輯,例如:

 
 
 
 
  1. public class StubProductRepository : IProductRepository   
  2. {   
  3.     public Product GetByID(string id)   
  4.     {   
  5.         if (id == "spr-product")   
  6.         {   
  7.             return new Product()   
  8.             {   
  9.                 ID = "spr-product",   
  10.                 Name = "Nice Product" 
  11.             };   
  12.         }   
  13.      
  14.         return null;   
  15.     }   
  16.      
  17.     public IEnumerable GetProducts()   
  18.     {   
  19.         throw new NotImplementedException();   
  20.     }   

在***種方法里,我們寫了兩個不同的IProductRepository模擬方法,而在第二種方法里,我們的邏輯變得有些復(fù)雜。如果我們在這些邏輯中犯了錯,那我們的測試就沒法得到正確的結(jié)果,這又為我們的調(diào)試增加了額外的負(fù)擔(dān),我們需要找到是業(yè)務(wù)代碼出來錯還是測試代碼不正確。

你也許還會質(zhì)疑這些模擬代碼中的這個沒有任何用處的 GetProducts()方法,它是干什么的?因?yàn)镮ProductRepository接口里有這個方法,我們不得不加入這個方法以讓程序能編譯通過——盡管在我們的測試中這個方法根本不是我們考慮到對象。

使用這樣的測試方法,我們不得不寫出大量的臨時模擬類,這無疑會讓我們在維護(hù)時愈加頭痛。這種時候,使用一個模擬框架,比如JustMock,將會節(jié)省我們大量的工作。

讓我們重新看一下之前的這個測試?yán)樱@次我們將使用一個模擬框架:

 
 
 
 
  1. [TestMethod]  
  2. public void GetProductWithValidIDReturnsProduct()  
  3. {  
  4.     // Arrange  
  5.     IProductRepository productRepository = Mock.Create();  
  6.     Mock.Arrange(() => productRepository.GetByID("spr-product")).Returns(new Product());  
  7.     ProductService productService = new ProductService(productRepository);  
  8.      
  9.     // Act  
  10.     Product product = productService.GetByID("spr-product");  
  11.      
  12.     // Assert  
  13.     Assert.IsNotNull(product);  
  14. }  
  15.      
  16. [TestMethod]  
  17. public void GetProductWithInValidIDThrowsException()  
  18. {  
  19.     // Arrange  
  20.     IProductRepository productRepository = Mock.Create();  
  21.     ProductService productService = new ProductService(productRepository);  
  22.      
  23.     // Act & Assert  
  24.     Assert.Throws(() => productService.GetByID("invalid-id"));  

有沒有注意到我們寫的代碼的減少量?在這個例子中代碼量減少49%,更準(zhǔn)確的說,使用模擬框架測試時代碼是28行,而沒有使用時是57行。我們還看到了整個測試方法變得可讀性更強(qiáng)了!

#p#

2、測試代碼組織的太松散

模擬框架讓我們在模擬測試中的生成某個依賴類的工作變得非常簡單,但有時候太輕易實(shí)現(xiàn)也容易產(chǎn)生壞處。為了說明這個觀點(diǎn),請觀察下面兩個單元測試,看看那一個容易理解。這兩個測試程序是測試一個相同的功能:

Test #1

 
 
 
 
  1. TestMethod]  
  2. public void InitializeWithValidProductIDReturnsView()  
  3. {  
  4.     // Arrange  
  5.     IProductView productView = Mock.Create();  
  6.     Mock.Arrange(() => productView.ProductID).Returns("spr-product");  
  7.      
  8.     IProductService productService = Mock.Create();  
  9.     Mock.Arrange(() => productService.GetByID("spr-product")).Returns(new Product()).OccursOnce();  
  10.      
  11.     INavigationService navigationService = Mock.Create();  
  12.     Mock.Arrange(() => navigationService.GoTo("/not-found"));  
  13.      
  14.     IBasketService basketService = Mock.Create();  
  15.     Mock.Arrange(() => basketService.ProductExists("spr-product")).Returns(true);  
  16.          
  17.     var productPresenter = new ProductPresenter(  
  18.                                             productView,  
  19.                                             navigationService,  
  20.                                             productService,   
  21.                                             basketService);  
  22.      
  23.     // Act  
  24.     productPresenter.Initialize();  
  25.      
  26.     // Assert  
  27.     Assert.IsNotNull(productView.Product);  
  28.     Assert.IsTrue(productView.IsInBasket);  

Test #2

 
 
 
 
  1. [TestMethod]  
  2. public void InitializeWithValidProductIDReturnsView()  
  3. {  
  4.     // Arrange     
  5.     var view = Mock.Create();  
  6.     Mock.Arrange(() => view.ProductID).Returns("spr-product");  
  7.      
  8.     var mock = new MockProductPresenter(view);  
  9.      
  10.     // Act  
  11.     mock.Presenter.Initialize();  
  12.      
  13.     // Assert  
  14.     Assert.IsNotNull(mock.Presenter.View.Product);  
  15.     Assert.IsTrue(mock.Presenter.View.IsInBasket);  

我相信Test #2是更容易理解的,不是嗎?而Test #1的可讀性不那么強(qiáng)的原因就是有太多的創(chuàng)建測試的代碼。在Test #2中,我把復(fù)雜的構(gòu)建測試的邏輯提取到了ProductPresenter類里,從而使測試代碼可讀性更強(qiáng)。

為了把這個概念說的更清楚,讓我們來看看測試中引用的方法:

 
 
 
 
  1. public void Initialize()  
  2. {  
  3.     string productID = View.ProductID;  
  4.     Product product = _productService.GetByID(productID);  
  5.      
  6.     if (product != null)  
  7.     {  
  8.         View.Product = product;  
  9.         View.IsInBasket = _basketService.ProductExists(productID);  
  10.     }  
  11.     else 
  12.     {  
  13.        NavigationService.GoTo("/not-found");  
  14.     }  

這個方法依賴于View, ProductService, BasketService and NavigationService等類,這些類都要模擬或臨時構(gòu)造出來。當(dāng)遇到這樣有太多的依賴關(guān)系時,這種需要寫出準(zhǔn)備代碼的副作用就會顯現(xiàn)出來,正如上面的例子。

請注意,這還只是個很保守的例子。更多的我看到的是一個類里有模擬一、二十個依賴的情況。

下面就是我在測試中提取出來的模擬ProductPresenter的MockProductPresenter類:

 
 
 
 
  1. public class MockProductPresenter  
  2. {  
  3.     public IBasketService BasketService { get; set; }  
  4.     public IProductService ProductService { get; set; }  
  5.     public ProductPresenter Presenter { get; private set; }  
  6.      
  7.     public MockProductPresenter(IProductView view)  
  8.     {  
  9.         var productService = Mock.Create();  
  10.         var navigationService = Mock.Create();  
  11.         var basketService = Mock.Create();  
  12.      
  13.         // Setup for private methods  
  14.         Mock.Arrange(() => productService.GetByID("spr-product")).Returns(new Product());  
  15.         Mock.Arrange(() => basketService.ProductExists("spr-product")).Returns(true);  
  16.         Mock.Arrange(() => navigationService.GoTo("/not-found")).OccursOnce();  
  17.      
  18.         Presenter = new ProductPresenter(  
  19.                                    view,  
  20.                                         navigationService,  
  21.                                         productService,  
  22.                                         basketService);  
  23.     }  

因?yàn)閂iew.ProductID的屬性值決定著這個方法的邏輯走向,我們向MockProductPresenter類的構(gòu)造器里傳入了一個模擬的View實(shí)例。這種做法保證了當(dāng)產(chǎn)品ID改變時自動判斷需要模擬的依賴。

我們也可以用這種方法處理測試過程中的細(xì)節(jié)動作,就像我們在第二個單元測試?yán)锏腎nitialize方法里處理product==null的情況:

 
 
 
 
  1. [TestMethod]  
  2. public void InitializeWithInvalidProductIDRedirectsToNotFound()  
  3. {  
  4.     // Arrange  
  5.     var view = Mock.Create();  
  6.     Mock.Arrange(() => view.ProductID).Returns("invalid-product");  
  7.      
  8.     var mock = new MockProductPresenter(view);  
  9.      
  10.     // Act  
  11.     mock.Presenter.Initialize();  
  12.      
  13.     // Assert  
  14.     Mock.Assert(mock.Presenter.NavigationService);  

這隱藏了一些ProductPresenter實(shí)現(xiàn)上的細(xì)節(jié)處理,測試方法的可讀性是***重要的。

#p#

3、一次測試太多的項(xiàng)目

看看下面的單元測試,請?jiān)诓皇褂谩昂汀边@個詞的情況下描述它:

 
 
 
 
  1. [TestMethod]  
  2. public void ProductPriceTests()  
  3. {  
  4.     // Arrange  
  5.     var product = new Product()  
  6.     {  
  7.         BasePrice = 10m  
  8.     };  
  9.      
  10.     // Act  
  11.     decimal basePrice = product.CalculatePrice(CalculationRules.None);  
  12.     decimal discountPrice = product.CalculatePrice(CalculationRules.Discounted);  
  13.     decimal standardPrice = product.CalculatePrice(CalculationRules.Standard);  
  14.      
  15.     // Assert  
  16.     Assert.AreEqual(10m, basePrice);  
  17.     Assert.AreEqual(11m, discountPrice);  
  18.     Assert.AreEqual(12m, standardPrice);  

我只能這樣描述這個方法:

“測試中計(jì)算基價,打折價和標(biāo)準(zhǔn)價是都能否返回正確的值。”

這是一個簡單的方法來判斷你是否一次測試了過多的內(nèi)容。上面這個測試會有三種情況導(dǎo)致它失敗。如果測試失敗,我們需要去找到那個/哪些出了錯。

理想情況下,每一個方法都應(yīng)該有它自己的測試,例如:

 
 
 
 
  1. [TestMethod]  
  2. public void CalculateDiscountedPriceReturnsAmountOf11()  
  3. {  
  4.     // Arrange  
  5.     var product = new Product()  
  6.     {  
  7.         BasePrice = 10m  
  8.     };  
  9.      
  10.     // Act  
  11.     decimal discountPrice = product.CalculatePrice(CalculationRules.Discounted);  
  12.      
  13.     // Assert  
  14.     Assert.AreEqual(11m, discountPrice);  
  15. }  
  16.      
  17. [TestMethod]  
  18. public void CalculateStandardPriceReturnsAmountOf12()  
  19. {  
  20.     // Arrange  
  21.     var product = new Product()  
  22.     {  
  23.         BasePrice = 10m  
  24.     };  
  25.      
  26.     // Act  
  27.     decimal standardPrice = product.CalculatePrice(CalculationRules.Standard);  
  28.      
  29.     // Assert  
  30.     Assert.AreEqual(12m, standardPrice);  
  31. }  
  32.      
  33. [TestMethod]  
  34. public void NoDiscountRuleReturnsBasePrice()  
  35. {  
  36.     // Arrange  
  37.     var product = new Product()  
  38.     {  
  39.         BasePrice = 10m  
  40.     };  
  41.      
  42.     // Act  
  43.     decimal basePrice = product.CalculatePrice(CalculationRules.None);  
  44.      
  45.     // Assert  
  46.     Assert.AreEqual(10m, basePrice);  

注意這些非常具有描述性的測試名稱。如果一個項(xiàng)目里有500個測試,其中一個失敗了,你能根據(jù)名稱就能知道哪個測試應(yīng)該為此承擔(dān)責(zé)任。

這樣我們可能會有更多的方法,但換來的好處是清晰。我在《代碼大全(第2版)》里看到了這句經(jīng)驗(yàn)之談:

為方法里的每個IF,And,Or,Case,F(xiàn)or,While等條件寫出獨(dú)立的測試方法。

驅(qū)動測試開發(fā)純粹主義者可能會說每個測試?yán)镏粦?yīng)該有一個斷言。我想這個原則有時候可以靈活處理,就像下面測試一個對象的屬性值時:

 
 
 
 
  1. public Product Map(ProductDto productDto)  
  2. {  
  3.     var product = new Product()  
  4.     {   
  5.         ID = productDto.ID,  
  6.         Name = productDto.ProductName,  
  7.         BasePrice = productDto.Price  
  8.     };  
  9.      
  10.     return product;  
  11. }  

我不認(rèn)為為每個屬性寫一個獨(dú)立的測試方法進(jìn)行斷言是有必要的。下面是我如何寫這個測試方法的:

 
 
 
 
  1. [TestMethod]  
  2. public void ProductMapperMapsToExpectedProperties()  
  3. {  
  4.     // Arrange  
  5.     var mapper = new ProductMapper();  
  6.     var productDto = new ProductDto()  
  7.     {  
  8.         ID = "sp-001",  
  9.         Price = 10m,  
  10.         ProductName = "Super Product" 
  11.     };  
  12.      
  13.     // Act  
  14.     Product product = mapper.Map(productDto);  
  15.      
  16.     // Assert  
  17.     Assert.AreEqual(10m, product.BasePrice);  
  18.     Assert.AreEqual("sp-001", product.ID);  
  19.     Assert.AreEqual("Super Product", product.Name);  

#p#

4、先寫程序后寫測試

我堅(jiān)持認(rèn)為,驅(qū)動測試開發(fā)的意義遠(yuǎn)高于測試本身。正確的實(shí)施驅(qū)動測試開發(fā)能巨大的提高開發(fā)效率,這是一種良性循環(huán)。我看到很多開發(fā)人員在開發(fā)完某個功能后才去寫測試方法,把這當(dāng)成一種在提交代碼前需要完成的行政命令來執(zhí)行。事實(shí)上,補(bǔ)寫測試代碼只是驅(qū)動測試開發(fā)的一個內(nèi)容。

如果不是按照先寫測試后寫被測試程序的紅,綠,重構(gòu)方法原則,測試編寫很可能會變成一種體力勞動。

如果想培養(yǎng)你的單元測試習(xí)慣,你可以看一些關(guān)于TDD的材料,比如The String Calculator Code Kata。

5、測試的過細(xì)

請檢查下面的這個方法:

 
 
 
 
  1. public Product GetByID(string id)  
  2. {  
  3.     return _productRepository.GetByID(id);  

這個方法真的需要測試嗎?不,我也認(rèn)為不需要。

驅(qū)動測試純粹主義者可能會堅(jiān)持認(rèn)為所有的代碼都應(yīng)該被測試覆蓋,而且有這樣的自動化工具能掃描并報告程序的某部分內(nèi)容沒有被測試覆蓋,然而,我們要當(dāng)心,不要落入這種給自己制造工作量的陷阱。

很多我交談過的反對驅(qū)動測試開發(fā)的人都會引用這點(diǎn)來作為不寫任何測試代碼的主要理由。我對他們的回復(fù)是:只測試你需要測試的代碼。我的觀點(diǎn)是,構(gòu)造器,geter,setter等方法沒必要特意的測試。讓我們來加深記憶一下我前面提到的經(jīng)驗(yàn)論:

為方法里的每個IF,And,Or,Case,F(xiàn)or,While等條件寫出獨(dú)立的測試方法。

如果一個方法里沒有任何一個上面提到的條件語句,那它真的需要測試嗎?

祝測試愉快!

獲取文中的代碼

文中例子的代碼你可以從這里找到。

英文原文:Top 5 TDD Mistakes

譯文鏈接:http://www.aqee.net/top-5-tdd-mistakes/


當(dāng)前文章:測試驅(qū)動開發(fā)上的五大錯誤
標(biāo)題網(wǎng)址:http://www.5511xx.com/article/ccessei.html