【Pro ASP.NET MVC 3 Framework】.学习笔记.3.MVC的主要工具-单元测试

IProductRepository接口定义了一个仓库,我们通过它获得、更新Product对象。IPriceReducer接口指定了一个功能,它将要对所有的Products实施,通过一个参数,降低他们的价格。

在这个例子中,我们的目的是创建一个IProductReducer的实现,它有以下功能:

  • 仓库中所有Product的价格都要减
  • 仓库的UpdateProduct方法可以被每个Product对象调用
  • 价格不能低于1

为了帮助我们建立实现,我们创建一个FakeRepository类,它实现了IProductRepository接口。

在ReducePrice方法上点右键,选择创建单元测试,在弹出对话框中选中要测试的方法,确定。单元测试会新建一个项目。

这里用到Enumerable.Zip<TFirst, TSecond, TResult> 方法。下面有一个列子:

1 int[] numbers = { 1, 2, 3, 4 }; 2 string[] words = { "one", "two", "three" }; 3 4 var numbersAndWords = numbers.Zip(words, (first, second) => first +""+ second); 5 6 foreach (var item in numbersAndWords) 7 Console.WriteLine(item); 8 9 // This code produces the following output: 10 11 // 1 one 12 // 2 two 13 // 3 three

像拉链一样,对比两个集合中,相同Index的元素。

1 [TestMethod] 2 publicvoid All_Prices_Are_Changed) { 3 4 // Arrange 5 FakeRepository repo =new FakeRepository(); 6 decimal reductionAmount =10; 7 IEnumerable<decimal> prices = repo.GetProducts().Select(e => e.Price); 8 decimal[] initialPrices = prices.ToArray(); 9 MyPriceReducer target =new MyPriceReducer(repo); 10 11 // Act 12 target.ReducePrices(reductionAmount); 13 14 prices.Zip(initialPrices, (p1, p2) => { 15 if (p1 == p2) { 16 Assert.Fail(); 17 } 18 return p1; 19 }); 20 }

这是我们第一个单元测试,它包含我们让VS查看的属性。TestClass属性被应用到类上,TestMethod属性被用在任何包含一个单元测试的方法上。被VS忽略的方法不用包含此属性。

你看到我们的单元测试方法遵循arrange/act/asert(A/A/A)模式。我们的单元测试方法叫做All_Prices_Are_Changed,通过Linq查询,调用ToArray得到FakeRepository类中包含的Product items的初始单价。然后,我们调用目标方法,使用Linq Zip方法,确保每个方法都改变了。如果任何元素没有改变,我们调用Asset.Fail方法,单元测试不及格。

建立单元测试有很多不同的方法,这里有一个公共的方法,用一个单一且伟大的方法,测试所有的必须条件。我们更喜欢创建一些小的单元测试,每个值关注程序的一方面。我们的偏爱来自两个原因,第一个是当一个小的单元测试不及格,你会准确地知道你代码中的欠缺。第二个原因是在我们查看了所有测试原因后,倾向于在多个测试方法中以破旧的代码结束,我们比代码中关注的更多,但是我们找到一个简洁的程序A/A/A模式。

1 ///<summary> 2 /// 检查优惠的总量是否相同 3 ///</summary> 4 [TestMethod] 5 publicvoid Correct_Total_Reduction_Amount() 6 { 7 FakeRepository repo =new FakeRepository(); 8 decimal reductionAmount =10; 9 decimal initialTotal = repo.GetTotalValue(); 10 MyPriceReducer target =new MyPriceReducer(repo); 11 12 //Act13 target.ReducePrices(reductionAmount); 14 15 //Assert16 Assert.AreEqual(repo.GetTotalValue(), (initialTotal - (repo.GetProducts().Count() * reductionAmount))); 17 } 18 19 ///<summary>20 /// 检查有没有单价低于1的产品 21 ///</summary>22 [TestMethod] 23 publicvoid No_Price_Less_Than_One_Dollar() 24 { 25 FakeRepository repo =new FakeRepository(); 26 decimal reductionAmount =decimal.MaxValue; 27 MyPriceReducer target =new MyPriceReducer(repo); 28 29 //Act30 target.ReducePrices(reductionAmount); 31 32 //Assert33 foreach(Product prod in repo.GetProducts()) 34 { 35 Assert.IsTrue(prod.Price >=1); 36 } 37 }

这些方法都遵循相同的模式。我们创建一个FakeRepository对象,并手工将它注入到MyPriceReducer类的构造器中。然后调用ReducePrices方法,使用Assert类的方法检查结果。下面你可能用到的静态方法,来检查或报告测试的状态。

Method Description
AreEqual<T>(T,T) AreEqual<T>(T,T,string) AreNotEqual<T>(T,T) AreNotEqual<T>(T,T,string) 断言连个T类型的对象,是否有相同的值
AreSame<T>(T,T) AreSame<T>(T,T,string)  AreNotSame<T>(T,T) AreNotSame<T>(T,T,string) 断言两个变量是否参照相同的对象
Fail() Fail(string) 不及格一个断言,没有条件被检查
Inconclusive() Inconclusive(string) 声明结果,当单元测试的结果不确定时
IsTrue(bool) IsTrue(bool,string) 断言为true,总是用于等价一个返回bool结果的表达式
IsFalse(bool) IsFalse(bool,string) 断言为false
IsNull(object) IsNotNull(object,string) 断言变量参照自object
IsInstanceOfType(object,Type) IsInstanceOfType(object,Type,string) IsNotInstanceOfType(object,Type) IsNotInstanceOfType(object,Type,string) 断言object是指定类型,或派生自指定类型

Assert类中的每个静态方法,允许我们检查单元测试中的一些方面。当断言不及格时,抛出出异常,这意味着单元测试整体不及格。每个单元测试都被分别对待,所以其他测试会继续执行。

每个方法都有一个带字符串参数的重载版本。字符串中包含断言不及格时,异常的message元素。AreEqual和AreNotEqual方法有一些重载来比较指定的类型。例如,这里有一个版本,它允许字符串被比较,不用考虑案例。

ExecptionExpected属性比较奇怪。只有在单元测试抛出ExceptionType参数指定类型的异常,断言成功。这是一个方便的方法,确保异常抛出,不需要用try…catch块瞎搞。

3 运行单元测试

将单元测试的项目设为启动项目,VS会在当前解决方案中为TestClass和TestMethod扫描所有的类。

posted @ 2013-08-26 08:55  Reinhard_Hsu  阅读(191)  评论(0编辑  收藏  举报