使用xUnit为.net core程序进行单元测试 -- Assert
第一部分: http://www.cnblogs.com/cgzl/p/8283610.html
Assert
Assert做什么?Assert基于代码的返回值、对象的最终状态、事件是否发生等情况来评估测试的结果。Assert的结果可能是Pass或者Fail。如果所有的asserts都pass了,那么整个测试就pass了;如果有任何assert fail了,那么测试就fail了。
xUnit提供了以下类型的Assert:
- boolean:True/False
- String:相等/不等,是否为空,以..开始/结束,是否包含子字符串,匹配正则表达式
- 数值型:相等/不等,是否在某个范围内,浮点的精度
- Collection:内容是否相等,是否包含某个元素,是否包含满足某种条件(predicate)的元素,是否所有的元素都满足某个assert
- Raised events:Custom events,Framework events(例如:PropertyChanged)
- Object Type:是否是某种类型,是否某种类型或继承与某种类型
一个test里应该有多少个asserts?
一种建议的做法是,每个test方法里面只有一个assert。
而还有一种建议就是,每个test里面可以有多个asserts,只要这些asserts都是针对同一个行为就行。
第一个Assert
目标类:

- public class Patient
- {
- public Patient()
- {
- IsNew = true;
- }
- public string FirstName { get; set; }
- public string LastName { get; set; }
- public string FullName => $"{FirstName} {LastName}";
- public int HeartBeatRate { get; set; }
- public bool IsNew { get; set; }
- public void IncreaseHeartBeatRate()
- {
- HeartBeatRate = CalculateHeartBeatRate() + 2;
- }
- private int CalculateHeartBeatRate()
- {
- var random = new Random();
- return random.Next(1, 100);
- }
- }

测试类:

- public class PatientShould
- {
- [Fact]
- public void HaveHeartBeatWhenNew()
- {
- var patient = new Patient();
- Assert.True(patient.IsNew);
- }
- }

运行测试:
结果符合预期,测试通过。
改为Assert.False()的话:
测试Fail。
String Assert
测试string是否相等:
- [Fact]
- public void CalculateFullName()
- {
- var p = new Patient
- {
- FirstName = "Nick",
- LastName = "Carter"
- };
- Assert.Equal("Nick Carter", p.FullName);
- }
然后你需要Build一下,这样VS Test Explorer才能发现新的test。
运行测试,结果Pass:
同样改一下Patient类(别忘了Build一下),让结果失败:
从失败信息可以看到期待值和实际值。
StartsWith, EndsWith
- [Fact]
- public void CalculateFullNameStartsWithFirstName()
- {
- var p = new Patient
- {
- FirstName = "Nick",
- LastName = "Carter"
- };
- Assert.StartsWith("Nick", p.FullName);
- }
- [Fact]
- public void CalculateFullNameEndsWithFirstName()
- {
- var p = new Patient
- {
- FirstName = "Nick",
- LastName = "Carter"
- };
- Assert.EndsWith("Carter", p.FullName);e);
- }
Build,然后Run Test,结果Pass:
忽略大小写 ignoreCase:
string默认的Assert是区分大小写的,这样就会失败:
可以为这些方法添加一个参数ignoreCase设置为true,就会忽略大小写:
包含子字符串 Contains
- [Fact]
- public void CalculateFullNameSubstring()
- {
- var p = new Patient
- {
- FirstName = "Nick",
- LastName = "Carter"
- };
- Assert.Contains("ck Ca", p.FullName);
- }
Build,测试结果Pass。
正则表达式,Matches
测试一下First name和Last name的首字母是不是大写的:
- [Fact]
- public void CalculcateFullNameWithTitleCase()
- {
- var p = new Patient
- {
- FirstName = "Nick",
- LastName = "Carter"
- };
- Assert.Matches("[A-Z]{1}{a-z}+ [A-Z]{1}[a-z]+", p.FullName);
- }
Build,测试通过。
数值 Assert
首先为Patient类添加一个property: BloodSugar。
- public class Patient
- {
- public Patient()
- {
- IsNew = true;
- _bloodSugar = 5.0f;
- }
- private float _bloodSugar;
- public float BloodSugar
- {
- get { return _bloodSugar; }
- set { _bloodSugar = value; }
- }
- ...
Equal:
- [Fact]
- public void BloodSugarStartWithDefaultValue()
- {
- var p = new Patient();
- Assert.Equal(5.0, p.BloodSugar);
- }
Build,测试通过。
范围, InRange:
首先为Patient类添加一个方法,病人吃饭之后血糖升高:
- public void HaveDinner()
- {
- var random = new Random();
- _bloodSugar += (float)random.Next(1, 1000) / 100; // 应该是1000
- }
添加test:
- [Fact]
- public void BloodSugarIncreaseAfterDinner()
- {
- var p = new Patient();
- p.HaveDinner();
- // Assert.InRange<float>(p.BloodSugar, 5, 6);
- Assert.InRange(p.BloodSugar, 5, 6);
- }
Build,Run Test,结果Fail:
可以看到期待的Range和实际的值,这样很好。如果你使用Assert.True(xx >= 5 && xx <= 6)
的话,错误信息只能显示True或者False。
因为HaveDinner方法里,表达式的分母应该是1000,修改后,Build,Run,测试Pass。
浮点型数值的Assert
在被测项目添加这两个类:
- namespace Hospital
- {
- public abstract class Worker
- {
- public string Name { get; set; }
- public abstract double TotalReward { get; }
- public abstract double Hours { get; }
- public double Salary => TotalReward / Hours;
- }
- public class Plumber : Worker
- {
- public override double TotalReward => ;
- public override double Hours => ;
- }
- }
然后针对Plumber建立一个测试类 PlumberShould.cs, 并建立第一个test:
- namespace Hospital.Tests
- {
- public class PlumberShould
- {
- [Fact]
- public void HaveCorrectSalary()
- {
- var plumber = new Plumber();
- Assert.Equal(66.666, plumber.Salary);
- }
- }
- }
Build项目, 然后再Test Explorer里面选择按Class分类显示Tests:
Run Selected Test, 结果会失败:
这是一个精度的问题.
在Assert.Equal方法, 可以添加一个precision参数, 设置精度为3:
- [Fact]
- public void HaveCorrectSalary()
- {
- var plumber = new Plumber();
- Assert.Equal(66.666, plumber.Salary, precision: 3);
- }
Build, Run Test:
因为有四舍五入的问题, 所以test仍然fail了.
所以还需要改一下:
- [Fact]
- public void HaveCorrectSalary()
- {
- var plumber = new Plumber();
- Assert.Equal(66.66, plumber.Salary, precision: );
- }
这次会pass的:
Assert Null值
- [Fact]
- public void NotHaveNameByDefault()
- {
- var plumber = new Plumber();
- Assert.Null(plumber.Name);
- }
- [Fact]
- public void HaveNameValue()
- {
- var plumber = new Plumber
- {
- Name = "Brian"
- };
- Assert.NotNull(plumber.Name);
- }
有两个方法, Assert.Null 和 Assert.NotNull, 直接传入期待即可.
测试会Pass的.
集合 Collection Assert
修改一下被测试类, 添加一个集合属性, 并赋值:
- namespace Hospital
- {
- public abstract class Worker
- {
- public string Name { get; set; }
- public abstract double TotalReward { get; }
- public abstract double Hours { get; }
- public double Salary => TotalReward / Hours;
- public List<string> Tools { get; set; }
- }
- public class Plumber : Worker
- {
- public Plumber()
- {
- Tools = new List<string>()
- {
- "螺丝刀",
- "扳子",
- "钳子"
- };
- }
- public override double TotalReward => ;
- public override double Hours => ;
- }
- }
测试是否包含某个元素, Assert.Contains():
- [Fact]
- public void HaveScrewdriver()
- {
- var plumber = new Plumber();
- Assert.Contains("螺丝刀", plumber.Tools);
- }
Build, Run Test, 结果Pass.
修改一下名字, 让其Fail:
这个失败信息还是很详细的.
相应的还有一个Assert.DoesNotContain()方法, 测试集合是否不包含某个元素.
- [Fact]
- public void NotHaveKeyboard()
- {
- var plumber = new Plumber();
- Assert.DoesNotContain("键盘", plumber.Tools);
- }
这个test也会pass.
Predicate:
测试一下集合中是否包含符合某个条件的元素:
- [Fact]
- public void HaveAtLeastOneScrewdriver()
- {
- var plumber = new Plumber();
- Assert.Contains(plumber.Tools, t => t.Contains("螺丝刀"));
- }
使用的是Assert.Contains的一个overload方法, 它的第一个参数是集合, 第二个参数是Predicate.
Build, Run Test, 会Pass的.
比较集合相等:
添加Test:
- [Fact]
- public void HaveAllTools()
- {
- var plumber = new Plumber();
- var expectedTools = new []
- {
- "螺丝刀",
- "扳子",
- "钳子"
- };
- Assert.Equal(expectedTools, plumber.Tools);
- }
注意, Plumber的tools类型是List, 这里的expectedTools类型是array.
这个test 仍然会Pass.
如果修改一个元素, 那么测试会Fail, 信息如下:
Assert针对集合的每个元素:
如果想对集合的每个元素进行Assert, 当然可以通过循环来Assert了, 但是更好的写法是调用Assert.All()方法:
- [Fact]
- public void HaveNoEmptyDefaultTools()
- {
- var plumber = new Plumber();
- Assert.All(plumber.Tools, t => Assert.False(string.IsNullOrEmpty(t)));
- }
这个测试会Pass.
如果在被测试类的Tools属性添加一个空字符串, 那么失败信息会是:
这里写到, 4个元素里面有1个没有pass.
针对Object类型的Assert
首先再添加一个Programmer类:
- public class Programmer : Worker
- {
- public override double TotalReward => ;
- public override double Hours => 3.5;
- }
然后建立一个WorkerFactory:
- namespace Hospital
- {
- public class WorkerFactory
- {
- public Worker Create(string name, bool isProgrammer = false)
- {
- if (isProgrammer)
- {
- return new Programmer { Name = name };
- }
- return new Plumber { Name = name };
- }
- }
- }
判断是否是某个类型 Assert.IsType<Type>(xx):
建立一个测试类 WorkerShould.cs和一个test:
- namespace Hospital.Tests
- {
- public class WorkerShould
- {
- [Fact]
- public void CreatePlumberByDefault()
- {
- var factory = new WorkerFactory();
- Worker worker = factory.Create("Nick");
- Assert.IsType<Plumber>(worker);
- }
- }
- }
Build, Run Test: 结果Pass.
相应的, 还有一个Assert.IsNotType<Type>(xx)方法.
利用Assert.IsType<Type>(xx)的返回值, 它会返回Type(xx的)的这个实例, 添加个一test:
- [Fact]
- public void CreateProgrammerAndCastReturnedType()
- {
- var factory = new WorkerFactory();
- Worker worker = factory.Create("Nick", isProgrammer: true);
- Programmer programmer = Assert.IsType<Programmer>(worker);
- Assert.Equal("Nick", programmer.Name);
- }
Build, Run Tests: 结果Pass.
Assert针对父类:
写这样一个test, 创建的是一个promgrammer, Assert的类型是它的父类Worker:
- [Fact]
- public void CreateProgrammer_AssertAssignableTypes()
- {
- var factory = new WorkerFactory();
- Worker worker = factory.Create("Nick", isProgrammer: true);
- Assert.IsType<Worker>(worker);
- }
这个会Fail:
这时就应该使用这个方法, Assert.IsAssignableFrom<祖先类>(xx):
- [Fact]
- public void CreateProgrammer_AssertAssignableTypes()
- {
- var factory = new WorkerFactory();
- Worker worker = factory.Create("Nick", isProgrammer: true);
- Assert.IsAssignableFrom<Worker>(worker);
- }
Build, Run Tests: Pass.
Assert针对对象的实例
判断两个引用是否指向不同的实例 Assert.NotSame(a, b):
- [Fact]
- public void CreateSeperateInstances()
- {
- var factory = new WorkerFactory();
- var p1 = factory.Create("Nick");
- var p2 = factory.Create("Nick");
- Assert.NotSame(p1, p2);
- }
由工厂创建的两个对象是不同的实例, 所以这个test会Pass.
相应的还有个Assert.Same(a, b) 方法.
Assert 异常
为WorkFactory先添加一个异常处理:
- namespace Hospital
- {
- public class WorkerFactory
- {
- public Worker Create(string name, bool isProgrammer = false)
- {
- if (name == null)
- {
- throw new ArgumentNullException(nameof(name));
- }
- if (isProgrammer)
- {
- return new Programmer { Name = name };
- }
- return new Plumber { Name = name };
- }
- }
- }
如果在test执行代码时抛出异常的话, 那么test会直接fail掉.
所以应该使用Assert.Throws<ArgumentNullException>(...)方法来Assert是否抛出了特定类型的异常.
添加一个test:
- [Fact]
- public void NotAllowNullName()
- {
- var factory = new WorkerFactory();
// var p = factory.Create(null); // 这个会失败- Assert.Throws<ArgumentNullException>(() => factory.Create(null));
- }
注意不要直接运行会抛出异常的代码. 应该在Assert.Throws<ET>()的方法里添加lambda表达式来调用方法.
这样的话就会pass.
如果被测试代码没有抛出异常的话, 那么test会fail的. 把抛异常代码注释掉之后再Run:
更具体的, 还可以指定参数的名称:
- [Fact]
- public void NotAllowNullName()
- {
- var factory = new WorkerFactory();
- // Assert.Throws<ArgumentNullException>(() => factory.Create(null));
- Assert.Throws<ArgumentNullException>("name", () => factory.Create(null));
- }
这里就是说异常里应该有一个叫name的参数.
Run: Pass.
如果把"name"改成"isProgrammer", 那么这个test会fail:
利用Assert.Throws<ET>()的返回结果, 其返回结果就是这个抛出的异常实例.
- [Fact]
- public void NotAllowNullNameAndUseReturnedException()
- {
- var factory = new WorkerFactory();
- ArgumentNullException ex = Assert.Throws<ArgumentNullException>(() => factory.Create(null));
- Assert.Equal("name", ex.ParamName);
- }
Assert Events 是否发生(Raised)
回到之前的Patient类, 添加如下代码:
- public void Sleep()
- {
- OnPatientSlept();
- }
- public event EventHandler<EventArgs> PatientSlept;
- protected virtual void OnPatientSlept()
- {
- PatientSlept?.Invoke(this, EventArgs.Empty);
- }
然后回到PatientShould.cs添加test:
- [Fact]
- public void RaiseSleptEvent()
- {
- var p = new Patient();
- Assert.Raises<EventArgs>(
- handler => p.PatientSlept += handler,
- handler => p.PatientSlept -= handler,
- () => p.Sleep());
- }
Assert.Raises<T>()第一个参数是附加handler的Action, 第二个参数是分离handler的Action, 第三个Action是触发event的代码.
Build, Run Test: Pass.
如果注释掉Patient类里Sleep()方法内部那行代码, 那么test会fail:
针对INotifyPropertyChanged的特殊Assert:
修改Patient代码:
- namespace Hospital
- {
- public class Patient: INotifyPropertyChanged
- {
- public Patient()
- {
- IsNew = true;
- _bloodSugar = 5.0f;
- }
- public string FirstName { get; set; }
- public string LastName { get; set; }
- public string FullName => $"{FirstName} {LastName}";
- public int HeartBeatRate { get; set; }
- public bool IsNew { get; set; }
- private float _bloodSugar;
- public float BloodSugar
- {
- get => _bloodSugar;
- set => _bloodSugar = value;
- }
- public void HaveDinner()
- {
- var random = new Random();
- _bloodSugar += (float)random.Next(, ) / ;
- OnPropertyChanged(nameof(BloodSugar));
- }
- public void IncreaseHeartBeatRate()
- {
- HeartBeatRate = CalculateHeartBeatRate() + ;
- }
- private int CalculateHeartBeatRate()
- {
- var random = new Random();
- return random.Next(, );
- }
- public void Sleep()
- {
- OnPatientSlept();
- }
- public event EventHandler<EventArgs> PatientSlept;
- protected virtual void OnPatientSlept()
- {
- PatientSlept?.Invoke(this, EventArgs.Empty);
- }
- public event PropertyChangedEventHandler PropertyChanged;
- protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
- {
- PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
- }
- }
- }
添加一个Test:
- [Fact]
- public void RaisePropertyChangedEvent()
- {
- var p = new Patient();
- Assert.PropertyChanged(p, "BloodSugar", () => p.HaveDinner());
- }
针对INotifyPropertyChanged, 可以使用Assert.PropertyChanged(..) 这个专用的方法来断定PropertyChanged的Event是否被触发了.
Build, Run Tests: Pass.
到目前为止, 介绍的都是入门级的内容.
接下来要介绍的是稍微进阶一点的内容了.
使用xUnit为.net core程序进行单元测试 -- Assert的更多相关文章
- 使用xUnit为.net core程序进行单元测试(上)
一. 导读 为什么要编写自动化测试程序(Automated Tests)? 可以频繁的进行测试 可以在任何时间进行测试,也可以按计划定时进行,例如:可以在半夜进行自动测试. 肯定比人工测试要快. 可以 ...
- 使用xUnit为.net core程序进行单元测试(1)
导读 为什么要编写自动化测试程序(Automated Tests)? 可以频繁的进行测试 可以在任何时间进行测试,也可以按计划定时进行,例如:可以在半夜进行自动测试. 肯定比人工测试要快. 可以更快速 ...
- 使用xUnit为.net core程序进行单元测试(中)
第一部分: http://www.cnblogs.com/cgzl/p/8283610.html 下面有一点点内容是重叠的.... String Assert 测试string是否相等: [Fact] ...
- 使用xUnit为.net core程序进行单元测试(3)
第1部分: http://www.cnblogs.com/cgzl/p/8283610.html 第2部分: http://www.cnblogs.com/cgzl/p/8287588.html 请使 ...
- 使用xUnit为.net core程序进行单元测试(4)
第1部分: http://www.cnblogs.com/cgzl/p/8283610.html 第2部分: http://www.cnblogs.com/cgzl/p/8287588.html 第3 ...
- 使用xUnit为.net core程序进行单元测试(2)
第一部分: http://www.cnblogs.com/cgzl/p/8283610.html 下面有一点点内容是重叠的.... String Assert 测试string是否相等: [Fact] ...
- 使用xUnit为.net core程序进行单元测试
第1部分: http://www.cnblogs.com/cgzl/p/8283610.html 第2部分: http://www.cnblogs.com/cgzl/p/8287588.html ...
- 好代码是管出来的——.Net Core中的单元测试与代码覆盖率
测试对于软件来说,是保证其质量的一个重要过程,而测试又分为很多种,单元测试.集成测试.系统测试.压力测试等等,不同的测试的测试粒度和测试目标也不同,如单元测试关注每一行代码,集成测试关注的是多个模块是 ...
- .NET Core: 在.NET Core中进行单元测试
单元测试能够帮助开发人员确保所开发的模块.类以及类中的方法等的正确性,在项目开发过程中,及时进行单元测试能够避免不必要的BUG以及提高测试效率. 在本文中,我们会分别来学习如何使用MSTest.xUn ...
随机推荐
- 自己动手写Redis客户端(C#实现)2 - SET请求和状态回复(set)
Redis请求协议的一般形式: *<参数数量> CR LF $<参数 的字节数量> CR LF <参数 的数据> CR LF ... $<参数 N 的字节数量 ...
- Log4j使用笔记:每天生成一个日志文件、按日志大小生成文件
其中TestLog4j.java如下: package cn.zhoucy.test; import org.apache.log4j.Logger; public class TestLog4j { ...
- maya cmds pymel 'ESC' 退出 while, for 循环
maya cmds pymel 'ESC' 退出 while, for 循环 import maya.cmds as cmds cmds.progressWindow(isInterruptable= ...
- HTC VIVE固定头显位置
用此方法可以限制HTC VIVE头显定位(即固定头显位置,但是视角是不固定的). UnityEngine.XR.InputTracking.disablePositionalTracking = fa ...
- 修改 bug 总结
对于 elementui table 组件的排序 要求对每列数据进行排序 数据是 金额之类 数字的没问题 但是 针对 标题中有数字,字母,文字的时候 会排序错误 :sort-method=" ...
- sqlzoo:5
展示世界的總人口. SELECT sum(population) FROM world 列出所有的洲份, 每個只有一次. select distinct(continent) from world 找 ...
- 百度语音合成AI
注意:不要使用Dw编辑PHP代码,会因为编码问题出错!!<?php require_once 'AipSpeech.php'; // 你的 APPID AK SK const APP_ID = ...
- js 事件模型详解
把js的事件模型,分为两类,DOM0级和DOM2级, DOM0级 通常直接在DOM对象上绑定函数对象,指定事件类型,dom.onClick = function(){};类似于这种写法,移除事件,则直 ...
- 4.28Linux(6)
2019-4-28 21:27:41 明天回家.回家继续学Linux还好有个服务器!!!感觉有个属于自己的服务器感觉好爽啊!! 越努力越幸运!永远不要高估自己!!! Nginx安装 服务器的请求原理 ...
- CSS-单位em 和 rem
1,em单位(css3新增单位) em:就是一种长度单位,它是参照当前元素的字号,如果没有设置,就参照父容器,一直到浏览器的默认字号大小 em 是相对长度单位(参照父元素),其参照当前元素字号大小,如 ...