分享web开发知识

注册/登录|最近发布|今日推荐

主页 IT知识网页技术软件开发前端开发代码编程运营维护技术分享教程案例
当前位置:首页 > 教程案例

使用xUnit为.net core程序进行单元测试(中)

发布时间:2023-09-06 01:37责任编辑:彭小芳关键词:暂无标签

第一部分: http://www.cnblogs.com/cgzl/p/8283610.html

下面有一点点内容是重叠的....

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 => 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的:

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 => 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.

针对Object类型的Assert

 首先再添加一个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) 方法.

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(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)); ???????} ???}}
View Code

添加一个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程序进行单元测试(中)

原文地址:https://www.cnblogs.com/cgzl/p/8287588.html

知识推荐

我的编程学习网——分享web前端后端开发技术知识。 垃圾信息处理邮箱 tousu563@163.com 网站地图
icp备案号 闽ICP备2023006418号-8 不良信息举报平台 互联网安全管理备案 Copyright 2023 www.wodecom.cn All Rights Reserved