第一部分: http://www.cnblogs.com/cgzl/p/8283610.htmlhtml
Assert作什么?Assert基于代码的返回值、对象的最终状态、事件是否发生等状况来评估测试的结果。Assert的结果多是Pass或者Fail。若是全部的asserts都pass了,那么整个测试就pass了;若是有任何assert fail了,那么测试就fail了。正则表达式
xUnit提供了如下类型的Assert:dom
一种建议的作法是,每一个test方法里面只有一个assert。ide
而还有一种建议就是,每一个test里面能够有多个asserts,只要这些asserts都是针对同一个行为就行。测试
目标类:ui
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); } }
测试类:this
public class PatientShould { [Fact] public void HaveHeartBeatWhenNew() { var patient = new Patient(); Assert.True(patient.IsNew); } }
运行测试:spa
结果符合预期,测试经过。3d
改成Assert.False()的话:code
测试Fail。
测试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,测试经过。
首先为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。
在被测项目添加这两个类:
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 => 200; public override double Hours => 3; } }
而后针对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.667, plumber.Salary, precision: 3); }
此次会pass的:
[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的.
修改一下被测试类, 添加一个集合属性, 并赋值:
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 => 200; public override double Hours => 3; } }
测试是否包含某个元素, 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.
首先再添加一个Programmer类:
public class Programmer : Worker { public override double TotalReward => 1000; 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) 方法.
为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); }
回到以前的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(1, 1000) / 1000; OnPropertyChanged(nameof(BloodSugar)); } public void IncreaseHeartBeatRate() { HeartBeatRate = CalculateHeartBeatRate() + 2; } private int CalculateHeartBeatRate() { var random = new Random(); return random.Next(1, 100); } 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.
到目前为止, 介绍的都是入门级的内容.
接下来要介绍的是稍微进阶一点的内容了.