推荐轻量友好的.NET测试断言工具Shouldly
Shouldly是一个轻量的断言(Assertion)框架,用于补充.NET框架下的测试工具。Shouldly将焦点放在当断言失败时如何简单精准的给出很好的错误信息。
Shouldly在GitHub的开源地址:https://github.com/shouldly/shouldly
Shouldly的官方文档:http://docs.shouldly-lib.net/
为什么要Shouldly?
我们知道通常测试代码中一个断言是这样写的:
Assert.That(contestant.Points, Is.EqualTo(1337));
当断言失败的时候,我们会得到这样的信息:
Expected 1337 but was 0
这样的信息让人烦恼的是,它够简练,但显然信息不够充分,可读性不高。
Shouldly充分利用了.NET框架的扩展方法,提供了更友好的处理方式。
用Shouldly编写的断言是这样的:
contestant.Points.ShouldBe(1337);
其中ShouldBe是一个扩展方法,也可以认为是一个易于理解的语法词汇。当断言失败的时候,我们会得到这样的信息:
contestant.Points should be 1337 but was 0
Shouldly的优势
下面的一个例子将两种断言方式放在一起,通过对比更容易发现Shouldly的好处:
Assert.That(map.IndexOfValue("boo"), Is.EqualTo(2)); // -> Expected 2 but was 1 map.IndexOfValue("boo").ShouldBe(2); // -> map.IndexOfValue("boo") should be 2 but was 1
Shouldly使用ShouldBe语句中的变量来报告错误,这使得更容易诊断。
另一个例子,如果你需要比较两个集合,采用Shouldly写法如下:
(new[] { 1, 2, 3 }).ShouldBe(new[] { 1, 2, 4 });
显然断言会失败,因为这两个集合并不相同。但Shouldly不仅如此,它会告诉你两个集合的差异所在:
should be [1, 2, 4] but was [1, 2, 3] difference [1, 2, *3*]
如果你想检查一个特殊的方法调用,它会或者不会抛出异常,它可以简单地这样写:
Should.Throw<ArgumentOutOfRangeException>(() => widget.Twist(-1));
这样就可以接触到异常,借此帮助你调试出潜在的异常来源。
详细用法
Shouldly断言框架提供了相等、迭代、动态变量、字符串、字典、任务/异步,以及异常等多方面的支持。要详细的了解这些可以访问Shouldly的官方文档:http://docs.shouldly-lib.net/
后续的断言例子基于下面的范例类:
1 namespace Simpsons 2 { 3 public abstract class Pet 4 { 5 public abstract string Name { get; set; } 6 7 public override string ToString() 8 { 9 return Name; 10 } 11 } 12 } 13 14 namespace Simpsons 15 { 16 public class Cat : Pet 17 { 18 public override string Name { get; set; } 19 } 20 } 21 22 namespace Simpsons 23 { 24 public class Dog : Pet 25 { 26 public override string Name { get; set; } 27 } 28 } 29 30 namespace Simpsons 31 { 32 public class Person 33 { 34 public string Name { get; set; } 35 public int Salary { get; set; } 36 37 38 public override string ToString() 39 { 40 return Name; 41 } 42 } 43 }
Equality 相等
ShouldBe
1 [Test] 2 public void ShouldBe() 3 { 4 var theSimpsonsCat = new Cat() { Name = "Santas little helper" }; 5 theSimpsonsCat.Name.ShouldBe("Snowball 2"); 6 }
失败信息:
Shouldly.ChuckedAWobbly : theSimpsonsCat.Name should be "Snowball 2" but was "Santas little helper"
ShouldNotBe
1 [Test] 2 public void ShouldNotBe() 3 { 4 var theSimpsonsCat = new Cat() { Name = "Santas little helper" }; 5 theSimpsonsCat.Name.ShouldNotBe("Santas little helper"); 6 }
失败信息:
Shouldly.ChuckedAWobbly : theSimpsonsCat.Name should not be "Santas little helper" but was "Santas little helper"
ShouldBeOfType
1 [Test] 2 public void ShouldBeOfType() 3 { 4 var theSimpsonsDog = new Cat() { Name = "Santas little helper" }; 5 theSimpsonsDog.ShouldBeOfType<Dog>(); 6 }
失败信息:
Shouldly.ChuckedAWobbly :
theSimpsonsDog
should be of type
Simpsons.Dog
but was
Simpsons.Cat
Enumberable 迭代
用于对列表集合进行断言
ShouldAllBe
1 [Test] 2 public void IEnumerable_ShouldAllBe_Predicate() 3 { 4 var mrBurns = new Person() { Name = "Mr.Burns", Salary=3000000}; 5 var kentBrockman = new Person() { Name = "Homer", Salary = 3000000}; 6 var homer = new Person() { Name = "Homer", Salary = 30000}; 7 var millionares = new List<Person>() {mrBurns, kentBrockman, homer}; 8 9 millionares.ShouldAllBe(m => m.Salary > 1000000); 10 }
失败信息:
Shouldly.ChuckedAWobbly : millionares should all be an element satisfying the condition (m.Salary > 1000000) but does not
ShouldContain
1 [Test] 2 public void IEnumerable_ShouldContain() 3 { 4 var mrBurns = new Person() { Name = "Mr.Burns", Salary=3000000}; 5 var kentBrockman = new Person() { Name = "Homer", Salary = 3000000}; 6 var homer = new Person() { Name = "Homer", Salary = 30000}; 7 var millionares = new List<Person>() {kentBrockman, homer}; 8 9 millionares.ShouldContain(mrBurns); 10 }
失败信息:
Shouldly.ShouldAssertException :
millionares
should contain
Mr.Burns
but does not
ShouldContain(Predicate)
1 [Test] 2 public void IEnumerable_ShouldContain_Predicate() 3 { 4 var homer = new Person() { Name = "Homer", Salary = 30000}; 5 var moe = new Person() { Name = "Moe", Salary=20000}; 6 var barney = new Person() { Name = "Barney", Salary = 0}; 7 var millionares = new List<Person>() {homer, moe, barney}; 8 9 // Check if at least one element in the IEnumerable satisfies the predicate 10 millionares.ShouldContain(m => m.Salary > 1000000); 11 }
失败信息:
Shouldly.ChuckedAWobbly : millionares should contain an element satisfying the condition (m.Salary > 1000000) but does not
Dynamic 动态对象
ShouldHaveProperty
1 [Test] 2 public void DynamicShouldHavePropertyTest() 3 { 4 var homerThinkingLikeFlanders = new ExpandoObject(); 5 DynamicShould.HaveProperty(homerThinkingLikeFlanders, "IAmABigFourEyedLameO"); 6 }
失败信息:
Shouldly.ChuckedAWobbly : Dynamic Object "homerThinkingLikeFlanders" should contain property "IAmABigFourEyedLameO" but does not.
String 字符串
ShouldMatch
1 [Test] 2 public void ShouldMatch() 3 { 4 var simpsonDog = new Dog() { Name = "Santas little helper" }; 5 simpsonDog.Name.ShouldMatch("Santas [lL]ittle Helper"); 6 }
失败信息:
Shouldly.ChuckedAWobbly : simpsonDog.Name should match "Santas [lL]ittle Helper" but was "Santas little helper"
ShouldBeNullOrEmpty
1 [Test] 2 public void ShouldBeNullOrEmpty() 3 { 4 var anonymousClanOfSlackJawedTroglodytes = new Person() {Name = "The Simpsons"}; 5 anonymousClanOfSlackJawedTroglodytes.Name.ShouldBeNullOrEmpty(); 6 }
失败信息:
Shouldly.ChuckedAWobbly : anonymousClanOfSlackJawedTroglodytes.Name should be null or empty
Dictionary 字典
ShouldNotContainKey
1 [Test] 2 public void ShouldNotContainKey() 3 { 4 var websters = new Dictionary<string, string>(); 5 websters.Add("Chazzwazzers", "What Australians would have called a bull frog."); 6 7 websters.ShouldNotContainKey("Chazzwazzers"); 8 }
失败信息:
Shouldly.ChuckedAWobbly : Dictionary "websters" should not contain key "Chazzwazzers" but does
ShouldContainKeyAndValue
1 [Test] 2 public void ShouldNotContainKey() 3 { 4 var websters = new Dictionary<string, string>(); 5 websters.Add("Chazzwazzers", "What Australians would have called a bull frog."); 6 7 websters.ShouldNotContainKey("Chazzwazzers"); 8 }
失败信息:
Shouldly.ChuckedAWobbly : Dictionary "websters" should not contain key "Chazzwazzers" but does
Task/Async 任务/异步
CompleteIn
1 [Test] 2 public void CompleteIn() 3 { 4 var homer = new Person() { Name = "Homer", Salary = 30000 }; 5 var denominator = 1; 6 Should.CompleteIn(() => 7 { 8 Thread.Sleep(2000); 9 var y = homer.Salary / denominator; 10 }, TimeSpan.FromSeconds(1)); 11 }
失败信息:
System.TimeoutException : The operation has timed out.
Exceptions 异常
ShouldThrow
1 [Test] 2 public void ShouldThrowFuncOfTask() 3 { 4 var homer = new Person() { Name = "Homer", Salary = 30000}; 5 var denominator = 1; 6 Should.Throw<DivideByZeroException>(() => 7 { 8 var task = Task.Factory.StartNew(() => { var y = homer.Salary/denominator; }); 9 return task; 10 }); 11 }
失败信息:
Shouldly.ChuckedAWobbly : Should throw System.DivideByZeroException but does not
ShouldNotThrow(Func<Task>)
这个方法支持异步方法,并且会等待操作执行直到完成。
1 [Test] 2 public void ShouldNotThrowFuncOfTask() 3 { 4 var homer = new Person() { Name = "Homer", Salary = 30000}; 5 var denominator = 0; 6 Should.NotThrow(() => 7 { 8 var task = Task.Factory.StartNew(() => { var y = homer.Salary/denominator; }); 9 return task; 10 }); 11 }
失败信息:
Shouldly.ChuckedAWobbly : Should not throw System.DivideByZeroException but does