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

 

转载于:https://www.cnblogs.com/defzhu/p/4841289.html

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
一些小事让生活变得轻松(在www.geocaching.com上)。 您可以自行选择要使用的功能。 所有功能均可通过易于使用的菜单进行配置。 全局为导航创建您自己的链接列表,就像原始导航一样,或作为其他下拉列表,从导航中删除原始gc.com链接。配置顺序,名称,...的链接,从列表中选择所有有意义的链接gc.com上的链接以及其他外部链接也有一些具有特殊功能的链接:Nearest-List,Nearest-Map,Nestest-List(无发现),用户搜索为弹出窗口创建自己的自定义链接导航中的搜索字段更改整个gc的宽度.com页面隐藏反馈按钮隐藏广告说明链接隐藏社交链接隐藏多余的换行符从google地图添加链接到geocaching.com配置页面以(取消激活)并配置所有功能配置中每个选项的小提示页(和指向德语帮助的链接)新版本的更新建议最近列表自动从最近列表重定向到地图记录最近链接中的It-Link书签最近列表中的It-Link更改找到的Cach的背景 变成绿色而不是灰色配置文件在侧边栏中的自己的链接列表在侧边栏中的可折叠框中编辑链接在自己的缓存旁边的画廊链接在自己的缓存旁边显示在公共配置文件中显示不同数量的硬币在缓存矩阵中显示“ n / 81”(统计页面) )在个人资料中建立电子邮件地址的链接在摘要页面中隐藏TB /硬币日志地图上的地图链接列表(选择要在地图上显示的链接)在地图上显示本地区域默认情况下隐藏找到/隐藏的缓存-缓存的默认设置-类型过滤器默认情况下隐藏边栏添加google和其他OSM地图选项以选择自定义默认地图山影和公共交通叠加层选项以隐藏未使用的地图图层选项以隐藏标题清单自动显示定义数量的日志“显示所有日志”-链接隐藏用户名旁边的头像邮件链接(高速缓存和TB列表)-还添加高速缓存/ TB名称,GCcode和所有者发送到邮件中隐藏空的高速缓存注释(通过链接显示)完全隐藏高速缓存注释显示高速缓存注释中的中断隐藏免责声明隐藏剧透战争 宁隐藏链接后的提示自动解密提示链接到Google地图直接在列表中记录日志缓存在TB列表gc.com中显示硬币系列的名称n修正了:解密提示中的换行符在列表中及之后在Dec和DMS中显示坐标打印页面显示“映射此位置”-链接已归档/禁用缓存的删除线标题显示列表VIP中的收藏点的百分比VIP-列表(您定义VIP,GClh在列表中显示一个列表,其中哪个VIP已找到缓存)显示在VIP上登录鼠标备用VIP样式:每个日志一个条目尚未找到缓存的VIP的可选列表“顶部”-日志中的链接突出显示列表和日志中的“相关网站”缩略图以及mouseover上的大图像(包括。 关键字过滤器,以防止显示破坏代码)按类型过滤显示的日志在日志中搜索更大的图像库中的图像将标题颜色更改为红色(如果已存档缓存)在事件缓存中显示星期几高亮显示已更改的坐标BBCode(用于创建列表)菜单隐藏绿色“至顶部”按钮-显示真实所有者而不是自由文本-改进了事件的google-calendar链接,从打印页中删除了页脚从缓存和TB的日志记录默认日志类型缓存和TB日志签名的使用#found_no#表示在日志/ TB签名和模板中找到的缓存数量,#found#表示在日志/ TB签名和模板+1中找到的缓存数量。#me#表示您在日志中的用户名/ TB-Signature和Templates直接在列表中记录缓存快捷方式:F2提交日志,PQ设置,书签表情符号和BBCode在日志字段旁边用户定义的Log-Templates将Tracking-Nummer传输到TB-Log-Field设置光标为对数字段中的第一个字符-您可以开始输入immedi TB / Coins的AutoVisit日志长度计数器Datepicker将最后一个日志另存为缓存和TB-Logs的模板,如果您离开页面而不保存日志,则警告该邮件形式邮件签名复选框的保存状态好友列表其他链接缓存数量为链接到发现列表链接到画廊链接到隐藏的缓存自上次访问以来发现的缓存数量书签“在google地图中显示”-链接收藏夹-列表显示缓存类型和总和的统计信息字段注释“全部选中”-复选框显示缓存类型和日志类型的统计信息用于保存PQ的Pocket Queries F2(如果由书签列表创建)将替换PQ-Name。 支持语言:English
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值