推荐轻量友好的.NET测试断言工具Shouldly

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

 

博文最后更新时间:


评论

  • Brenton

    Thank you for writing this awesome article. I'm a long time reader but I've never been compelled to leave a comment. I subscribed to your blog and shared this on my Facebook. Thanks again for a great post! https://amassante.com/comment-soulager-une-entorse-lombaire/

  • Merziuz

    rDzvdI http://pills2sale.com/ viagra online

  • Rigoberto

    Which team do you support? <a href="http://www.atozfruitsandveg.com/brahmi-powder-health-benefits-ylft">hindu brahmin marriage rituals</a> And there are still touches of the old version of iOS throughout the new iOS 7. For example, the Messages app still uses speech bubbles, and the camera app still uses an on-screen shutter button. But the look of those features have been spruced up as well.

  • Mauricio

    I like it a lot <a href="http://us.webpage-demo.com/communityschools/alli-weight-loss-pills-p1jo">survey.medallia.con/staples-cares</a> In testimony during a hearing at the U.S. Securities andExchange Commission, an SEC official alleged that DeloitteTouche Tohmatsu LLP's failure to turn over audit records hasstalled a three-year-old probe into financial fraud at a largesolar power company.

  • Cecil

    I was born in Australia but grew up in England <a href="https://unef.edu.br/site/caravans-for-sale-in-winkups-towyn-v9eu">static caravans on site for sale cornwall</a> Aguilera debuted her slim figure in a tight pink dress at an NBC press event in Beverly Hills Saturday. She still rocked her stunning curves, but this miniskirt showed she has definitely been focused on tightening her legs.

  • Jayson

    Can I use your phone? <a href="https://conference.ue-varna.bg/emc/cipro-online-76eg">ciprofloxacin 500mg for sore throat</a> PDVSA's tight tanker situation has become more acute since2012, when several fires and an explosion at its refineriesdamaged more than a dozen storage tanks. That has forced thecompany to use vessels to store oil.

发表评论

博客统计

访问量:5263936

博文总数:750 评论总数:910100

原创126 翻译20 转载604