前面也有說"控制反轉"所謂的依賴注入(Dependency Injection)簡稱DI。針對它有一款不錯的容器,那就是"Ninject",這東西是一個簡單易用的東西。話不多說,直接開始吧!
使用Ninject
先用控制台程序玩玩首先定義一個類,接口以及它的實現,代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Ninject_Tools { public class Product : Object { public int ProductID { get; set; } public string Name { get; set; } public string Description { get; set; } public decimal Price { get; set; } public string Category { get; set; } } }
下面創建一個接口,具體代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Ninject_Tools { public interface IValueCalculator { decimal ValueProducts(params Product[] products); } }
注:上面的IValueCalculator里面多了一個ValueProducts的方法,用於返回商品的累計值。
接着我們寫一個類來實現接口IValueCalculator,代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Ninject_Tools { public class LinqValueCalculator : IValueCalculator { public decimal ValueProducts(params Product[] products) { return products.Sum(h => h.Price); } } }
注:這里用LINQ的擴張方法來計算Prducts商品的總價,當然還有其他的辦法也可以實現。
接下來我們需要創建一個類來實現依賴注入,具體的代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Ninject_Tools { public class ShoppingCart : Object { private IValueCalculator calcuator; public ShoppingCart(IValueCalculator calcParam) { this.calcuator = calcParam; } public decimal CalculateStockValue() { //計算產品集總和 Product[] products = { new Product(){Name="Huitai",Price=1M}, new Product(){Name="ShuaiShuai",Price=10M}, new Product(){Name="Jack",Price=100M}, new Product(){Name="Cena",Price=200M} }; //計算產品總和 decimal totaValue = this.calcuator.ValueProducts(products); //返回結果 return totaValue; } } }
注:ShoppingCart構造器接受接口IValueCalculator的實現的實例作為參數來構造注入來實現ShoppingCart與LinqValueCalculator(IValueCalculator接口實現)的解耦,這也就是依賴注入里的"構造注入(Constructor Injection)".它們之間的關系如下圖1.
圖1.
由上面的圖可以看出ShoppingCart類和LinqValueCalculator類都依賴於IValueCalcutor,ShoppingCart和LinqValueCalculator沒有直接關系,甚至都不知道他的存在。
下面我們就需要添加Ninject到我們的項目里來玩玩,在我們的應用程序里使用管理程序"NuGet"包來引入Ninject的DLL,如下圖2.給我們的應用程序來加載進來Ninject.dll.
圖2.
加載進來Ninject.dll,然后我們就需要開始使用它了。
然后我們在控制台程序的Mian方法里使用它,具體的代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Ninject; namespace Ninject_Tools { class Program { static void Main(string[] args) { //創建Ninject實例,方便我們下面使用 IKernel ninjectKerenl = new StandardKernel(); //綁定相關的類型和創建的接口 ninjectKerenl.Bind<IValueCalculator>().To<LinqValueCalculator>(); //得到接口的實現 IValueCalculator calcImpl = ninjectKerenl.Get<IValueCalculator>(); //創建實例ShoppingCart實例依賴注入 ShoppingCart cart = new ShoppingCart(calcImpl); //輸出 Console.WriteLine("Total:{0:C}", cart.CalculateStockValue()); } } }
好了,現在跑一下我們的程序,可以看到如下圖3.的結果。
圖3.
小結一下吧!我們開始解耦,然后將IValueCalcutor的實例對象作為參數傳遞給ShoppingCart的構造器的方式,然后我們通過"依賴注入"容器來處理這個參數,在上面我們用Ninject來實現的,到這里相信大家對NinJect有一點初步的認識。
創建依賴鏈(Creating Chains of Dependency)
當向Ninject創建一個類型,它檢查該類型與其它類型之間的耦合關系,類型和其他類型。如果有額外的依賴,Ninject解決它們並創建新的我們需要的類的實例。
給我們的控制台程序繼續添加一個新的接口IDiscountHelper進來,代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Ninject_Tools { public interface IDiscountHelper { decimal ApplyDiscount(decimal totalParam); } }
然后在添加一個實現新接口IDiscountHelper的類DefaultDiscountHelper進來,代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Ninject_Tools { public class DefaultDiscountHelper : IDiscountHelper { public decimal ApplyDiscount(decimal totalParam) { return (totalParam - (10m / 100m * totalParam)); } } }
然后在我們上面的LinqValueCalculator類里添加依賴,代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Ninject_Tools { public class LinqValueCalculator : IValueCalculator { private IDiscountHelper discounter; //LinqValueCalculator構造器接受IDiscountHelper實例對象 public LinqValueCalculator(IDiscountHelper discountParam) { this.discounter = discountParam; } public decimal ValueProducts(params Product[] products) { return this.discounter.ApplyDiscount(products.Sum(h => h.Price)); } } }
然后就是綁定實現接口,具體代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Ninject; namespace Ninject_Tools { class Program { static void Main(string[] args) { //創建Ninject實例,方便我們下面使用 IKernel ninjectKerenl = new StandardKernel(); //綁定相關的類型和創建的接口 ninjectKerenl.Bind<IValueCalculator>().To<LinqValueCalculator>(); ninjectKerenl.Bind<IDiscountHelper>().To<DefaultDiscountHelper>(); //得到接口的實現 IValueCalculator calcImpl = ninjectKerenl.Get<IValueCalculator>(); //創建實例ShoppingCart實例依賴注入 ShoppingCart cart = new ShoppingCart(calcImpl); //輸出 Console.WriteLine("Total:{0:C}", cart.CalculateStockValue()); } } }
然后在跑下我們修改后的程序,結果如下圖4.
圖4.
小結一下吧:代碼中Ninject將兩個接口分別綁定到對應的實現類中,我們沒有改變實現IValueCalculator類的代碼。當我們請求IValueCalculator的類型時Ninject知道就實例化LinqValueCalculator對象來返回。但是它會去檢查這個類,發現LinqValueCalculator還同時依賴另一個接口,並且這個接口是它能解析的,Ninject創建一個DefaultDiscountHelper的實例注入LinqValueCalculator類的構造器,並返回IValueCalculator類型的對象。Ninject會只用這種方式檢查每一個要實例化的類的依賴,不管依賴鏈有多的復雜。
指定屬性和參數值(Specifying Property and Parameter Values)
我們可以配置類提供一些屬性或者具體的值傳給Ninject,當我們綁定和接口的實現。然后我們對上面的DefaultDiscountHelper類寫死的折扣經行修改。
然后我們給DefaultDiscountHelper類添加一條屬性,代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Ninject_Tools { public class DefaultDiscountHelper : IDiscountHelper { public decimal DiscountSize { get; set; } public decimal ApplyDiscount(decimal totalParam) { return (totalParam - (this.DiscountSize / 100m * totalParam)); } } }
然后在我們綁定實現接口的時候,就可以動態的使用WithPropertyValue來動態的設置我們的折扣力度。代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Ninject; namespace Ninject_Tools { class Program { static void Main(string[] args) { //創建Ninject實例,方便我們下面使用 IKernel ninjectKerenl = new StandardKernel(); //綁定相關的類型和創建的接口 ninjectKerenl.Bind<IValueCalculator>().To<LinqValueCalculator>(); ninjectKerenl.Bind<IDiscountHelper>().To<DefaultDiscountHelper>().WithPropertyValue("DiscountSize",50M); //得到接口的實現 IValueCalculator calcImpl = ninjectKerenl.Get<IValueCalculator>(); //創建實例ShoppingCart實例依賴注入 ShoppingCart cart = new ShoppingCart(calcImpl); //輸出 Console.WriteLine("Total:{0:C}", cart.CalculateStockValue()); } } }
添加了屬性的項目跑起來看是不是我們輸入5折的折扣,運行起來如下圖5.
圖5.
使用自動綁定(Self-Binding)
一個有用的功能整合到你的代碼中完全是Ninject自我綁定,也就是來自Ninject內核被請求的類的地方。這似乎是一個奇怪的事情,但是這意味着我們不用手動的執行DI初始化,空值太程序可以修改如下代碼所示:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Ninject; namespace Ninject_Tools { class Program { static void Main(string[] args) { //創建Ninject實例,方便我們下面使用 IKernel ninjectKerenl = new StandardKernel(); //綁定相關的類型和創建的接口 ninjectKerenl.Bind<IValueCalculator>().To<LinqValueCalculator>(); ninjectKerenl.Bind<IDiscountHelper>().To<DefaultDiscountHelper>().WithPropertyValue("DiscountSize", 50M); ////得到接口的實現 //IValueCalculator calcImpl = ninjectKerenl.Get<IValueCalculator>(); ////創建實例ShoppingCart實例依賴注入 //ShoppingCart cart = new ShoppingCart(calcImpl); ////輸出 //這里是調用ShoppingCart本身,所以注釋掉這行代碼程序也可以執行,但是調用具體的類經行自我綁定,代碼如下: //ninjectKernel.Bind<ShoppingCart>().ToSelf().WithParameter("<parameterName>", <paramvalue>); ninjectKerenl.Bind<ShoppingCart>().ToSelf(); ShoppingCart cart = ninjectKerenl.Get<ShoppingCart>(); Console.WriteLine("Total:{0:C}", cart.CalculateStockValue()); } } }
運行程序可以看到如下圖6.所示的結果
圖6.
綁定派生類型(Binding to a Derived Type)
前面一直都在接口綁定的方面(因為接口在MVC里相關性更強),我們也可以使用Ninject綁定具體類。Self-Binding就是這樣搞的。那樣我們可以使用Ninject綁定一個具體的類到它的派生類,因為前面我們綁定接口到實現它的具體類--實現類繼承了該接口,這里就是類繼承類罷了。那我們修改一下ShoppingCart類,代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Ninject_Tools { public class ShoppingCart : Object { protected IValueCalculator calcuator; protected Product[] products; public ShoppingCart(IValueCalculator calcParam) { this.calcuator = calcParam; //計算產品集總和 Product[] products = { new Product(){Name="Huitai",Price=1M}, new Product(){Name="ShuaiShuai",Price=10M}, new Product(){Name="Jack",Price=100M}, new Product(){Name="Cena",Price=200M} }; } public virtual decimal CalculateStockValue() { //計算產品總和 decimal totaValue = this.calcuator.ValueProducts(products); //返回結果 return totaValue; } } }
寫一個LimitShoppingCart類派生自ShoppingCart類:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Ninject_Tools { public class LimitShoppingCart : ShoppingCart { public LimitShoppingCart(IValueCalculator calcParam) : base(calcParam) { } public override decimal CalculateStockValue() { //過濾掉價格超過我們設定價格的商品然后在求和 var filteredProducts = products.Where(h => h.Price < ItemLimit); return this.calcuator.ValueProducts(filteredProducts.ToArray()); } public decimal ItemLimit { get; set; } } }
然后綁定ShoppingCart到它的派生類LimitShoppingCart類,代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Ninject; namespace Ninject_Tools { class Program { static void Main(string[] args) { //創建Ninject實例,方便我們下面使用 IKernel ninjectKerenl = new StandardKernel(); //綁定相關的類型和創建的接口 ninjectKerenl.Bind<IValueCalculator>().To<LinqValueCalculator>(); ninjectKerenl.Bind<IDiscountHelper>().To<DefaultDiscountHelper>().WithPropertyValue("DiscountSize", 50M); ////得到接口的實現 //IValueCalculator calcImpl = ninjectKerenl.Get<IValueCalculator>(); ////創建實例ShoppingCart實例依賴注入 //ShoppingCart cart = new ShoppingCart(calcImpl); ////輸出 //這里是調用ShoppingCart本身,所以注釋掉這行代碼程序也可以執行,但是調用具體的類經行自我綁定,代碼如下: //ninjectKernel.Bind<ShoppingCart>().ToSelf().WithParameter("<parameterName>", <paramvalue>); //ninjectKerenl.Bind<ShoppingCart>().ToSelf(); ninjectKerenl.Bind<ShoppingCart>().To<LimitShoppingCart>().WithPropertyValue("ItemLimit", 100M); ShoppingCart cart = ninjectKerenl.Get<ShoppingCart>(); Console.WriteLine("Total:{0:C}", cart.CalculateStockValue()); } } }
使用條件綁定(Using Conditional Binding)
我們可對同一個接口會有多重實現或者是對同一個類有多個派生,這時Ninject可以指定不同的條件來說明哪一個應該被使用。比如下面,我們創建一個新的類IterativeValueCalculator實現IValueCalculator,代碼如下:

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Ninject_Tools { public class IterativeValueCalculator : IValueCalculator { public decimal ValueProducts(params Product[] products) { decimal totalValue = 0; foreach (Product p in products) { totalValue += p.Price; } return totalValue; } } }
下面是對IValueCalculator的兩個不同實現了,可以通過Ninject設置條件來指定哪個顯示被應用。

using System; using System.Collections.Generic; using System.Linq; using System.Text; using Ninject; namespace Ninject_Tools { class Program { static void Main(string[] args) { //創建Ninject實例,方便我們下面使用 IKernel ninjectKerenl = new StandardKernel(); //綁定相關的類型和創建的接口 ninjectKerenl.Bind<IValueCalculator>().To<LinqValueCalculator>(); //在這里綁定可以使用條件綁定 ninjectKerenl.Bind<IValueCalculator>().To<IterativeValueCalculator>().WhenInjectedInto<ShoppingCart>(); //ninjectKerenl.Bind<IDiscountHelper>().To<DefaultDiscountHelper>().WithPropertyValue("DiscountSize", 50M); ////得到接口的實現 //IValueCalculator calcImpl = ninjectKerenl.Get<IValueCalculator>(); ////創建實例ShoppingCart實例依賴注入 //ShoppingCart cart = new ShoppingCart(calcImpl); ////輸出 //這里是調用ShoppingCart本身,所以注釋掉這行代碼程序也可以執行,但是調用具體的類經行自我綁定,代碼如下: //ninjectKernel.Bind<ShoppingCart>().ToSelf().WithParameter("<parameterName>", <paramvalue>); //ninjectKerenl.Bind<ShoppingCart>().ToSelf(); ninjectKerenl.Bind<ShoppingCart>().To<LimitShoppingCart>().WithPropertyValue("ItemLimit", 1000M); ShoppingCart cart = ninjectKerenl.Get<ShoppingCart>(); Console.WriteLine("Total:{0:C}", cart.CalculateStockValue()); } } }
從上面的代碼可以看出,綁定指定IterativeValueCalculator類應該被實例化在綁定IValueCalculatorinterface時被當對象的依賴注入。如果我們的條件沒有一個合適也沒有關系,Ninject會尋找一個對該類或接口的默認的綁定,以至於Ninject會有一個返回的結果。
在Asp.Net MVC 中使用Ninject及運用單元測試
前面是都是控制台程序里面玩Ninject,在MVC玩Ninject可就沒那么簡單了,比起來就稍微有點復雜了。開始是我們需要創建一個類,它的派生自System.Web.Mvc.DefaultControllerFactory。該類MVC依賴默認情況下創建控制器類的實例。.Net有好多單元測試包,其中又由好多包開放源代碼,但願測試工具最受歡迎那可能就是NUnit。我們就玩玩怎么創建但願測試和填充測試。
首先先看看在Asp.Net MVC 中如何使用Ninject,開始需要創建一個類,讓他繼承DefaultControllerFactory ,代碼如下:

using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using Ninject; using System.Web.Routing; namespace MVC_Tools.Models { public class NinjectControllerFactory : DefaultControllerFactory { private IKernel ninjectKernel; public NinjectControllerFactory() { this.ninjectKernel = new StandardKernel(); AddBindings(); } protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) { return controllerType == null ? null : (IController)ninjectKernel.Get(controllerType); } private void AddBindings() { //把額外的東西綁定到這里 this.ninjectKernel.Bind<IProductRepository>().To<FakeProductRepository>(); } } }
這個類創建一個Ninject內核,通過GetControllerInstance方法為控制器類的請求服務,這個方法在需要一個控制器對象的時候被MVC框架調用。我們不需要顯式地使用Ninject綁定控制器類。我們可以依靠默認的self-binding(自我綁定)特性,因為controllersare具體類派生自 System.Web.Mvc.Controller。AddBindings()方法允許我們綁定Repositories和其他需要保持松耦合的組件。我們也可以使用這個方法綁定需要額外的構造器參數或屬性參數的controller classes。
一旦我們創建這個類,我們必須注冊它與MVC框架,我們需要在Global.asax文件測試它,具體代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Routing; using MVC_Tools.Models; namespace MVC_Tools { // 注意: 有關啟用 IIS6 或 IIS7 經典模式的說明, // 請訪問 http://go.microsoft.com/?LinkId=9394801 public class MvcApplication : System.Web.HttpApplication { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleErrorAttribute()); } public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( "Default", // 路由名稱 "{controller}/{action}/{id}", // 帶有參數的 URL new { controller = "Home", action = "Index", id = UrlParameter.Optional } // 參數默認值 ); } protected void Application_Start() { AreaRegistration.RegisterAllAreas(); RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); ControllerBuilder.Current.SetControllerFactory(new NinjectControllerFactory()); } } }
現在MVC框架將使用我們的NinjectControllerFactoryto獲得實例的控制器類,Ninject會處理迪向控制器對象自動。有關這東西就先了解到這里吧!
下面看我們的單元測試,弄一個控制台程序來搞吧!
創建相關的Product類,IPriceReducer,IProductRepository接口如下面:

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Text { public class Product : Object { public int ProductID { get; set; } public string Name { get; set; } public string Description { get; set; } public decimal Price { get; set; } public string Category { set; get; } } }

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Text { public interface IPriceReducer { void ReducePrices(decimal priceReduction); } }

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Text { public interface IProductRepository { IEnumerable<Product> GetProducts(); void UpdateProduct(Product product); } }
IProductRepository定義了一個存儲庫,我們通過將獲取和更新Product對象。IPriceReducer定義了一個具體的降價方法,針對所有的Products。我們的目標就是實現這些接口並需要遵循下面的情況:
- 所有的Product都應該降價
- 總共的價格必須等於所有的Product數量乘以降價數額的乘積
- 降價后的Products必須大於1美元
- Repository的UpdateProduct方法應該被每一個Product商品調用到
然后我們添加一個實現接口IProductRepository的類FakeRepository,代碼如下:

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Text { public class FakeRepository : IProductRepository { private Product[] products = { new Product() { Name = "Kayak", Price = 275M}, new Product() { Name = "Lifejacket", Price = 48.95M}, new Product() { Name = "Soccer ball", Price = 19.50M}, new Product() { Name = "Stadium", Price = 79500M} }; public IEnumerable<Product> GetProducts() { return products; } public void UpdateProduct(Product productParam) { foreach (Product p in products .Where(e => e.Name == productParam.Name) .Select(e => e)) { p.Price = productParam.Price; } UpdateProductCallCount++; } public int UpdateProductCallCount { get; set; } public decimal GetTotalValue() { return products.Sum(e => e.Price); } } }
還需要添加一個類MyPriceReducer去實現IPriceReducer接口,代碼如下:

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Text { public class MyPriceReducer : IPriceReducer { private IProductRepository repository; public MyPriceReducer(IProductRepository repo) { repository = repo; } public void ReducePrices(decimal priceReduction) { throw new NotImplementedException(); } } }
開始我們的單元測試吧!我們要按照TDD模式和編寫單元測試代碼之前編寫應用程序,點擊MyPriceReducer類里的ReducePrices方法,然后選擇"創建單元測試",如下圖6.
圖6.當選擇"創建單元測試"會出現如下圖7.所示的彈出框,
圖7.由於單元測試是一個單獨的項目,所以我們給他取名"Test_Project",,創建完成后會新建,一個單元測試的項目加載進來,並生成了一個測試類MyPriceReducerTest,它里面包含一些屬性和方法讓供我們使用,具體生成代碼如下:

using Text; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; namespace Test_Project { /// <summary> ///這是 MyPriceReducerTest 的測試類,旨在 ///包含所有 MyPriceReducerTest 單元測試 ///</summary> [TestClass()] public class MyPriceReducerTest { private TestContext testContextInstance; /// <summary> ///獲取或設置測試上下文,上下文提供 ///有關當前測試運行及其功能的信息。 ///</summary> public TestContext TestContext { get { return testContextInstance; } set { testContextInstance = value; } } #region 附加測試特性 // //編寫測試時,還可使用以下特性: // //使用 ClassInitialize 在運行類中的第一個測試前先運行代碼 //[ClassInitialize()] //public static void MyClassInitialize(TestContext testContext) //{ //} // //使用 ClassCleanup 在運行完類中的所有測試后再運行代碼 //[ClassCleanup()] //public static void MyClassCleanup() //{ //} // //使用 TestInitialize 在運行每個測試前先運行代碼 //[TestInitialize()] //public void MyTestInitialize() //{ //} // //使用 TestCleanup 在運行完每個測試后運行代碼 //[TestCleanup()] //public void MyTestCleanup() //{ //} // #endregion /// <summary> ///ReducePrices 的測試 ///</summary> [TestMethod()] public void ReducePricesTest() { IProductRepository repo = null; // TODO: 初始化為適當的值 MyPriceReducer target = new MyPriceReducer(repo); // TODO: 初始化為適當的值 Decimal priceReduction = new Decimal(); // TODO: 初始化為適當的值 target.ReducePrices(priceReduction); Assert.Inconclusive("無法驗證不返回值的方法。"); } } }
但是對我們來說最重要莫過於項目了,所以我們將代碼修改成下面的形式:

using Text; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Linq; using System.Collections; using System.Collections.Generic; namespace Test_Project { [TestClass] public class MyPriceReducerTest { [TestMethod] public void All_Prices_Are_Changed() { FakeRepository repo = new FakeRepository(); decimal reductionAmount = 10; IEnumerable<decimal> prices = repo.GetProducts().Select(h => h.Price); decimal[] initialPrices = prices.ToArray(); MyPriceReducer target = new MyPriceReducer(repo); target.ReducePrices(reductionAmount); //逐一比較開始和降價后的價格,如果相等說明降價失敗 prices.Zip(initialPrices, (p1, p2) => { if (p1 == p2) { Assert.Fail(); } return p1; }); } } }
運行單元測試的的代碼,結果如下圖8.所示
圖8.因為我們沒有去實現ReducePrices,單元測試不能通過。有很多不同的方式來構建單元測試年代。常見的是有一個方法是測試所需的所有功能的條件。但是我們更喜歡創建很多小的單元測試,每個單元測試只關注應用程序的一個方面。當讓給單元測試方法名字的命名規則根據自己的編程風格自己定義,只要符合命名規范即可。把測試的全部功能分散到一個一個的小的測試方法里經行,通過這種方式不斷的完善我們的驅動測試開發(TDD).如下面的代碼:

using Text; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Linq; using System.Collections; using System.Collections.Generic; namespace Test_Project { [TestClass] public class MyPriceReducerTest { [TestMethod] public void All_Prices_Are_Changed() { FakeRepository repo = new FakeRepository(); decimal reductionAmount = 10; IEnumerable<decimal> prices = repo.GetProducts().Select(h => h.Price); decimal[] initialPrices = prices.ToArray(); MyPriceReducer target = new MyPriceReducer(repo); target.ReducePrices(reductionAmount); //逐一比較開始和降價后的價格,如果相等說明降價失敗 prices.Zip(initialPrices, (p1, p2) => { if (p1 == p2) { Assert.Fail(); } return p1; }); } [TestMethod] public void Correct_Total_Reduction_Amount() { FakeRepository repo = new FakeRepository(); decimal reductionAmount = 10; decimal initialToatal = repo.GetTotalValue(); MyPriceReducer tatget = new MyPriceReducer(repo); tatget.ReducePrices(reductionAmount); Assert.AreEqual(repo.GetTotalValue(), (initialToatal - (repo.GetProducts().Count() * reductionAmount))); } public void No_Price_Less_Than_One_Dollar() { FakeRepository repo = new FakeRepository(); decimal reductionAmount = decimal.MaxValue; MyPriceReducer target = new MyPriceReducer(repo); target.ReducePrices(reductionAmount); foreach (Product prod in repo.GetProducts()) { Assert.IsTrue(prod.Price >= 1); } } } }
然后運行我們的單元測試代碼,結果如圖9.
圖9.其實上面的單元的測試代碼里也使用了簡單依賴注入(構造器注入),上面的每一個測試方法就是針對一個功能來測試,貌似測試沒有通過,那是因為我們的ReducePrices方法沒有實現,現在我們就是去搞下他吧!代碼具體如下:

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Text { public class MyPriceReducer:IPriceReducer { private IProductRepository repository; public MyPriceReducer(IProductRepository repo) { repository = repo; } public void ReducePrices(decimal priceReduction) { //throw new NotImplementedException(); //實現功能 foreach (var item in repository.GetProducts()) { item.Price = Math.Max(item.Price - priceReduction, 1); repository.UpdateProduct(item); } } } }
搞完之后,在來跑下我們的單元測試的模塊,運行結果如下圖10.
圖10.呵呵!其實這些基本都是VS里面的東西,差不多隨便看看點點,寫寫就會明白的吧!好了就先分享這么一點東西吧!大家共同學習進步,文章里那里要是有錯誤還是那里描述錯誤的,請路過的朋友們,前輩們多多指導,批評。這樣我們才能更好的學習。