第四話 Asp.Net MVC 3.0【MVC基本工具及單元測試】


前面也有說"控制反轉"所謂的依賴注入(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,代碼如下:

 

View Code
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設置條件來指定哪個顯示被應用。

View Code
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 ,代碼如下:

View Code
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接口如下面:

View Code
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; }
    }
}
View Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Text
{
    public interface IPriceReducer
    {
        void ReducePrices(decimal priceReduction);
    }
}
View Code
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,代碼如下:

View Code
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接口,代碼如下:

View Code
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,它里面包含一些屬性和方法讓供我們使用,具體生成代碼如下:

View Code
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("無法驗證不返回值的方法。");
        }
    }
}

 但是對我們來說最重要莫過於項目了,所以我們將代碼修改成下面的形式:

View Code
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).如下面的代碼:

View Code
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方法沒有實現,現在我們就是去搞下他吧!代碼具體如下:

View Code
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里面的東西,差不多隨便看看點點,寫寫就會明白的吧!好了就先分享這么一點東西吧!大家共同學習進步,文章里那里要是有錯誤還是那里描述錯誤的,請路過的朋友們,前輩們多多指導,批評。這樣我們才能更好的學習。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM