推荐轻量友好的.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 

 

posted @ 2015-09-26 21:18  朱优良  阅读(2559)  评论(0编辑  收藏  举报