自强不息,厚德载物!身心自在,道法自然!


第四话 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里面的东西,差不多随便看看点点,写写就会明白的吧!好了就先分享这么一点东西吧!大家共同学习进步,文章里那里要是有错误还是那里描述错误的,请路过的朋友们,前辈们多多指导,批评。这样我们才能更好的学习。

posted @ 2012-06-30 09:46  辉太  阅读(2666)  评论(5编辑  收藏  举报

路漫漫其修远兮,吾将上下而求索!