《Pro ASP.NET MVC 3 Framework》学习笔记之十【Unit Testing的使用】

前面的笔记做了关于Ninject(MVC三类工具里面第一类IoC容器),本次的笔记是关于VS里面提供的Unit Testing工具的使用以及Moq(模拟工具)。

1.Visual Studio自带的单元测试工具

除了使用微软自带的单元测试工具,我们还可以选择NUnit--非常流行的一款测试工具。接下来我们创建一个项目ProductApp,你也可以使用NUnit,猛击这里获取。它的使用跟VS自带的非常类似。

首先我们创建用来的测试的类和接口,如下所示:

public class Product { 

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; } } 

public interface IProductRepository{ 

IEnumerable<Product> GetProducts(); void UpdateProduct(Product product); } 

public interface IPriceReducer { 

void ReducePrices(decimal priceReduction); } 

这里的Product类跟前面笔记里面的是一样的,IProductRepository接口定义两个方法:分别用来获取和更新。这个符合Repository模式,我们在前面的笔记里面介绍的。IPriceReducer定义了一个具体的降价方法,针对所有的Products。我们的目的就是要实现这个接口并满足下面的条件:

a.所有的Products都要降价  

b.总的降价必须等于所有的Products数量与降价数额的乘积  

c.Repository的UpdateProduct方法应该能被每一个product调用

d.降价后所有的Products的价格不能低于1

接下来我们添加一个实现IProductRepository的类,如下所示:

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);     } }

如果你跟我一样对3.0里面出现的查询表达式LINQ不熟悉的话,我建议你花时间看下相关的资料,前面5天我在补习这方面的知识,为了方便学习MVC,而且了解了LINQ以后你会慢慢喜欢LINQ的。下面接着是实现IPriceReducer接口,如下所示:

public class MyPriceReducer : IPriceReducer { private IProductRepository repository; 

public MyPriceReducer(IProductRepository repo)     {         repository = repo;     } 

public void ReducePrices(decimal priceReduction)     { //这里先不去实现        throw new NotImplementedException();     } }

这里我们又可以看到DI,而且是通过构造器参数来实现依赖注入的(如果你比较对DI比较生疏,可以看看我前面做的笔记)。

下面我们开始进入测试的正题了,创建一个测试项目。方法很简单,下图是一种:

在我们刚才的那个没有具体实现的方法上右键创建单元测试即可。接下来的操作如图:

单元测试是在一个单独的项目里面,这里我们没有创建所以会弹出创建该测试项目的对话框,我们给它取名为:ProductApp.Tests.创建完成后会自动创建一个MyPriceReducerTest.cs类。里面默认会生成一些方法,我们更改如下:

namespace ProductApp.Tests { 

[TestClass] public class MyPriceReducerTest     {         //测试是否是所有的价格都会发生改变         [TestMethod] public void All_Prices_Are_Changed)         { 

// Arrange             FakeRepository repo = new FakeRepository(); decimal reductionAmount = 10;             IEnumerable<decimal> prices = repo.GetProducts().Select(e => e.Price); decimal[] initialPrices = prices.ToArray();             MyPriceReducer target = new MyPriceReducer(repo); 

// Act              target.ReducePrices(reductionAmount);             //合并两个序列(4.0里面新提供的扩展方法)            //这里是将开始的价格跟降价后的价格逐一对比,如果有相等的说明降价失败。                    prices.Zip(initialPrices, (p1, p2) => { if (p1 == p2) {                     Assert.Fail();                 } return p1;             });                 }     } }

这里我们可以发现测试方法上面的会标记一些特性(Attributes),如[TestMethod],[TestClass]。如果测试的项目里面的类没有包括这些特性的类和方法是不会测试的,VS会忽略它们。我们可以发现是按照arrange/act/assert的模式来进行单元测试的。arrange:初始化测试的环境属于准备阶段;act:执行测试;assert:断言,测试的结果。对于测试方法的约定有很多种,我们这里的是简单的,通过方法名就能够知道测试的内容是什么。其实如果你不喜欢大可以选择自己的方式或者是团队的约定,主要是为了开发人员能够理解。

关于创建单元测试有很多不同的方式,一种通常的方式就是在一个方法里面测试某一功能的所有情况。这里我们倾向将很多不同的情况分散到一个一个小的测试方法里面来进行。这种通过测试不断来完善我们的代码的开发模式成为TDD(测试驱动开发).接着创建测试方法,代码如下:

[TestMethod] public void Correct_Total_Reduction_Amount() { 

// Arrange     FakeRepository repo = new FakeRepository(); decimal reductionAmount = 10; decimal initialTotal = repo.GetTotalValue();     MyPriceReducer target = new MyPriceReducer(repo); 

// Act     target.ReducePrices(reductionAmount); 

// Assert     Assert.AreEqual(repo.GetTotalValue(),(initialTotal - (repo.GetProducts().Count() * reductionAmount)));} 

[TestMethod] public void No_Price_Less_Than_One_Dollar() { 

// Arrange     FakeRepository repo = new FakeRepository(); decimal reductionAmount = decimal.MaxValue;     MyPriceReducer target = new MyPriceReducer(repo); 

// Act     target.ReducePrices(reductionAmount); 

// Assert     foreach (Product prod in repo.GetProducts()) {         Assert.IsTrue(prod.Price >= 1);     } }

上面的两个测试方法应该好理解吧。这里同样实现了依赖注入,而且是构造器注入。关于Assert静态方法还有很多,我们可以猛击这里了解。每一个静态方法让我们针对单元测试的一个方面来测试,如果Assert失败就会抛出一个异常,这也就意味着整个单元测试失败。每一个单元测试是独立的,其他的单元测试不会因为某一个单元测试的失败而停止。

Assert的每一个静态方法都有一个重载的带有string参数的方法,这个参数包含了Assert失败时的异常信息。值得注意的是这个ExceptionExpected的特性,这是一个断言,只有当单元测试通过异常类型的参数抛出一个具体化的类型的异常才成功。这是一种非常灵巧的方式,它确保了抛出异常不需要在我们的测试代码里面混入try...catch.

接下来我们运行测试,这时会报错,因为我们有个方式还没实现呢?接着实现该方法,代码如下:

public class MyPriceReducer : IPriceReducer { private IProductRepository repository; 

 public MyPriceReducer(IProductRepository repo)  {  repository = repo;  } 

public void ReducePrices(decimal priceReduction)     { foreach (Product p in repository.GetProducts())         {             p.Price = Math.Max(p.Price - priceReduction, 1); //既能降价而且可以保证每个Product的价格不低于1元            repository.UpdateProduct(p);         }     } }

再一次请大家注意这里的加黑部分实现了构造器注入的。

好了,本次的笔记就到这里。今天的主要介绍了VS自带的测试工具,比较容易理解。本来没打算记这部分的笔记,但为了保持整个笔记的连贯,还是写了。如果你已经对测试比较熟悉了,抱歉浪费你时间了。后面还有一个Moq工具的介绍,之后会有一个小的应用:SportsStore.

笔记中肯定会有我理解不准确或错误的地方,还请路过的朋友多多指导帮助,谢谢!

晚安!

时间: 2024-11-01 05:05:46

《Pro ASP.NET MVC 3 Framework》学习笔记之十【Unit Testing的使用】的相关文章

ASP.NET MVC 3 Framework学习笔记之Model Templates

.使用模板化的视图Helpers(Using Templated View Helpers) 模版化视图helpers的创意就是它们更加灵活.我们不用自己去指定应该用什么HTML元素来呈现一个模型的属性,MVC自己会搞定,在我们更新了视图模型时,也不用手动的更新视图.下面是一个例子:  代码如下 复制代码 //在Models里面添加Persons.cs using System; using System.Collections.Generic; using System.Linq; using

《Pro ASP.NET MVC 3 Framework》学习笔记目录

<Pro ASP.NET MVC 3 Framework>简介: 作者: Adam Freeman 和 Steven Sanderson 出版社: Apress; New 平装: 820页 语种: 英语 ISBN: 1430234040 声明:笔记里面按我自己的理解翻译了大部分内容,写这个笔记的目的:为了方便自己查阅,也为园友提供学习的方便. 我无意侵犯作者的任何权利,仅仅为了自己学习.也希望路过的朋友不要用于任何商业目的. 第一部分 ASP.NET MVC3介绍   <Pro ASP.

《Pro ASP.NET MVC 3 Framework》学习笔记之一【MVC的历程,优点,HelloWorld】

序论:asp.net mvc出现已经有两三年的时间了(2009开始1.0版本),但是这么方面的中文学习资料仍然非常少,特别是asp.net mvc3,几乎就没有中文的学习书籍.在英文的书籍中有两本是非常经典的mvc3教程:<Professional ASP.NET MVC 3>--作者:Jon Galloway , Phil Haack, Brad Wilson , K. Scott Allen和<Pro ASP.NET MVC 3 Framework>--作者:Steven Sa

ASP.NET MVC Web API 学习笔记----HttpClient简介

  1. HttpClient简单介绍  依稀还记得那个时候用WebClient,HttpWebRequest来发送一个请求,现在ASP.NET MVC4中自带了一个类HttpClient,用于接收HttpResponseMessage和发送HttpRequestMesssage. 问题在于既然WebClient,HttpWebRequest可以完成相应的功能,为什么还要使用HttpClient类,.NET Framework中既然提出了这样一个类肯定是有其特别之处的,这里罗列几个不同之处: (

ASP.NET MVC Web API 学习笔记---联系人增删改查

本章节简单介绍一下使用ASP.NET MVC Web API 做增删改查.目前很多Http服务还是通过REST或者类似RESP的模型来进行数据操作的.下面我们通过创建一个简单的Web API来管理联系人          说明:为了方便数据不使用真正的数据库,而是通过内存数据模拟    1.       Web API中包含的方法 Action HTTP method Relative URI GetAllContact GET /api/contact GetContact GET /api/

《Pro ASP.NET MVC 3 Framework》学习笔记之九【Ninject的使用-下】

接着上次的Ninject的笔记,如果你是初次路过,可以先看看我前面的笔记. 一,创建依赖链(Chains of Dependency) 当我们向Ninject请求创建一个类型时,Ninject会去检查该类型和其他类型之间的耦合关系.如果有额外的依赖,Ninject也会解析它们并创建我们需要的所有类的实例.为了进一步说明,我们创建一个新的接口和一个实现该接口的类.请注意我们的例子是跟前面的笔记衔接的,所以如果你打算跟着一起操作的话,最好能够去看看前面的笔记. 创建一个IDiscountHelper

《Pro ASP.NET MVC 3 Framework》学习笔记之四【领域模型介绍】

主题:应用领域驱动开发(Applying Domain-Driven Development) Domain Model是MVC程序的"心脏",其他的一切,包括Controllers和Views仅仅是用来跟Domain Model交互的一种方式,ASP.NET MVC并没有限制使用在Domain Model上面的技术,我们可以自由的选择跟.net framework交互的技术,并且这样的选择是非常多的.不仅如此,ASP.NET MVC为我们提供了基础的架构和约定来帮助Domain Mo

《Pro ASP.NET MVC 3 Framework》学习笔记之五【依赖注入及ninject工具使用】

一,创建松耦合的组件 1."分解关注点"是MVC模式里面一个非常重要的特性.我们想要在应用程序里面创建的组件尽可能的独立,这样我们就能管理比较少的依赖关系.理想情况下,每个组件都是孤立的,不知道其他组件的存在,处理应用程序的其他领域仅仅通过抽象接口,这就是所谓的松耦合,它让我们的应用程序更加容易测试和修改.通过一个简单的例子可以帮助我们理解,假如我们想写一个发邮件的组件,暂且就把这个组件命名为MyEmailSender,接着我们实现一个接口,这个接口定义了所有需要发送邮件的功能,也暂且

《Pro ASP.NET MVC 3 Framework》学习笔记之三十五 【部署】

准备要部署的应用程序 在正式进入部署MVC程序到IIS之前,会介绍一些关于应用程序迁移到生产环境之前探测错误以及一旦进入生产环境最大化性能的技术.同时也会展示关于流线型部署过程的有用的功能.   检测视图错误 Razor视图会在服务器需要的时候编译而不是在VS里面生成项目时编译,正常情况下,探测视图编译错误的方式是系统的访问每一个action,从而让每一个view都能够呈现.这显然是非常乏味而且不会一直成功的技术,特别是在基于不同的model状态呈现不同的view的时候.我们可以启用一个特别的项

《Pro ASP.NET MVC 3 Framework》学习笔记之十五【示例项目SportsStore】

绑定Shopping Cart 定义购物车Cart的实体,购物车是我们程序业务领域的一个部分,所以在我们领域模型(Domain Model)里面添加一个cart的实体是合理的.在SportsStore.Domain的Entities文件夹下添加一个Cart的实体类,如下所示: View Code public class Cart {private List<CartLine> lineCollection = new List<CartLine>();//添加 public vo