xUnit.net是针对.NET Framework的免费,开源,以社区为中心的单元测试工具。git
单元测试能够测试某个类或方法,具备较高的深度,对应用的功能覆盖面很小。
集成测试有更好的广度,能够测试web资源,数据库资源等。
皮下测试在web中针对controller下的节点测试。
UI测试是对应用的界面功能测试。
实际上经常使用的是单元测试和集成测试。web
通常是针对类的Public方法进行测试,也就是对行为进行测试,若是是私有方法须要改变修饰符才能测试正则表达式
.Net Framework
.Net Core
.Net Standard
UWP
Xamarin数据库
官网:
https://xunit.netc#
VS自带的测试浏览器(右键测试或者ctrl+r,t) resharper, cmd命令行(.net cli): dotnet test dotnet test --help
namespace Demo { public class Calculator { public int Add(int x,int y) { return x + y; } } }
public class CalculatorTests { [Fact] public void ShouldAddEquals5() //注意命名规范 { //Arrange var sut = new Calculator(); //sut-system under test,通用命名 //Act var result = sut.Add(3, 2); //Assert Assert.Equal(5, result); } }
Arrange: 在这里作一些先决的设定。例如建立对象实例,数据,输入等。
Act: 在这里执行生产代码并返回结果。例如调用方法或者设置属性。
Assert:在这里检查结果,会产生测试经过或者失败两种结果。浏览器
Assert基于代码的返回值、对象的最终状态、事件是否发生等状况来评估测试的结果
Assert的结果多是Pass或者Fail
若是全部的asserts都经过了,那么整个测试就经过了。
若是任何assert 失败了,那么结果就失败了。bash
一个test里应该有多少个assertside
演示示例:
先建一个.net core类库项目,再创建一个xunit测试项目(参考最后综合示例)函数
[Fact] [Trait("Category","New")] public void BeNewWhenCreated() { _output.WriteLine("第一个测试"); // Arrange var patient = new Patient(); // Act var result = patient.IsNew; // Assert Assert.True(result); }
[Fact] public void HaveCorrectFullName() { //var patient = new Patient(); _patient.FirstName = "Nick"; _patient.LastName = "Carter"; var fullName = _patient.FullName; Assert.Equal("Nick Carter", fullName); //相等 Assert.StartsWith("Nick", fullName);//以开头 Assert.EndsWith("Carter", fullName);//以结尾 Assert.Contains("Carter", fullName);//包含 Assert.Contains("Car", fullName); Assert.NotEqual("CAR", fullName);//不相等 Assert.Matches(@"^[A-Z][a-z]*\s[A-Z][a-z]*", fullName);//正则表达式 }
[Fact] [Trait("Category", "New")] public void HaveDefaultBloodSugarWhenCreated() { var p = new Patient(); var bloodSugar = p.BloodSugar; Assert.Equal(4.9f, bloodSugar,5); //判断是否相等 Assert.InRange(bloodSugar, 3.9, 6.1);//判断是否在某一范围内 }
[Fact] public void HaveNoNameWhenCreated() { var p = new Patient(); Assert.Null(p.FirstName); Assert.NotNull(_patient); }
[Fact] public void HaveHadAColdBefore() { //Arrange var _patient = new Patient(); //Act var diseases = new List<string> { "感冒", "发烧", "水痘", "腹泻" }; _patient.History.Add("发烧"); _patient.History.Add("感冒"); _patient.History.Add("水痘"); _patient.History.Add("腹泻"); //Assert //判断集合是否含有或者不含有某个元素 Assert.Contains("感冒",_patient.History); Assert.DoesNotContain("心脏病", _patient.History); //判断p.History至少有一个元素,该元素以水开头 Assert.Contains(_patient.History, x => x.StartsWith("水")); //判断集合的长度 Assert.All(_patient.History, x => Assert.True(x.Length >= 2)); //判断集合是否相等,这里测试经过,说明是比较集合元素的值,而不是比较引用 Assert.Equal(diseases, _patient.History); }
/// <summary> /// 测试Object /// </summary> [Fact] public void BeAPerson() { var p = new Patient(); var p2 = new Patient(); Assert.IsNotType<Person>(p); //测试对象是否相等,注意这里为false Assert.IsType<Patient>(p); Assert.IsAssignableFrom<Person>(p);//判断对象是否继承自Person,true //判断是否为同一个实例 Assert.NotSame(p, p2); //Assert.Same(p, p2); }
/// <summary> /// 判断是否发生异常 /// </summary> [Fact] public void ThrowException() //注意不能使用ctrl+R,T快捷键,由于会中断测试,抛出异常 { var p = new Patient(); //判断是否返回指定类型的异常 var ex = Assert.Throws<InvalidOperationException>(()=> { p.NotAllowed(); }); //判断异常信息是否相等 Assert.Equal("not able to create", ex.Message); }
/// <summary> /// 判断是否触发事件 /// </summary> [Fact] public void RaizeSleepEvent() { var p = new Patient(); Assert.Raises<EventArgs>( handler=>p.PatientSlept+=handler, handler=>p.PatientSlept -= handler, () => p.Sleep()); }
/// <summary> /// 测试属性改变事件是否触发 /// </summary> [Fact] public void RaisePropertyChangedEvent() { var p = new Patient(); Assert.PropertyChanged(p, nameof(p.HeartBeatRate), () => p.IncreaseHeartBeatRate()); }
使用trait特性,对测试进行分组:[Trait("Name","Value")] 能够做用于方法级和Class级别
相同的分组使用相同的特性。工具
[Fact] [Trait("Category","New")] public void BeNewWhenCreated() { _output.WriteLine("第一个测试"); // Arrange //var patient = new Patient(); // Act var result = _patient.IsNew; // Assert Assert.True(result); //Assert.False(result); }
测试分组搜索: 能够在测试资源管理器中按分组排列、搜索、运行测试
在dotnet cli中分组测试:
dotnew test --filter “Category=New” //运行单个分类测试 dotnew test --filter “Category=New|Category=Add”//运行多个分类测试 dotnet test --filter Category --logger:trx //输出测试日志
使用特性:[Fact(Skip="不跑这个测试")],能够忽略测试,忽略测试图标为黄色警告
使用ITestOutputHelper能够自定义在测试时的输出内容
dotnet test --filter Category --logger:trx会输出测试日志trx结尾的文件
public class PatientShould:IClassFixture<LongTimeFixture>,IDisposable { private readonly ITestOutputHelper _output; private readonly Patient _patient; private readonly LongTimeTask _task; public PatientShould(ITestOutputHelper output,LongTimeFixture fixture) { this._output = output; _patient = new Patient(); //_task = new LongTimeTask(); _task = fixture.Task; } [Fact] [Trait("Category","New")] public void BeNewWhenCreated() { _output.WriteLine("第一个测试"); // Arrange //var patient = new Patient(); // Act var result = _patient.IsNew; // Assert Assert.True(result); //Assert.False(result); } }
在执行一个方法时,须要很长事件,而在构造函数中new时,每一个测试跑的时候都会new对象或者执行方法,这是致使测试很慢。解决方法:
using Demo2; using System; namespace Demo2Test { public class LongTimeFixture : IDisposable { public LongTimeTask Task { get; } public LongTimeFixture() { } public void Dispose() { } } }
public class PatientShould:IClassFixture<LongTimeFixture>,IDisposable { private readonly ITestOutputHelper _output; private readonly Patient _patient; private readonly LongTimeTask _task; public PatientShould(ITestOutputHelper output,LongTimeFixture fixture) { this._output = output; _patient = new Patient(); //_task = new LongTimeTask(); _task = fixture.Task;//获取方法 } }
1.在上一个的继承上,先创建一个TaskCollection类,实现ICollectionFixture<LongTimeFixture>接口,注意不能有反作用,不然会影响结果
using Xunit; namespace Demo2Test { [CollectionDefinition("Lone Time Task Collection")] public class TaskCollection:ICollectionFixture<LongTimeFixture> { } }
[Collection("Lone Time Task Collection")] public class PatientShould:IClassFixture<LongTimeFixture>,IDisposable { private readonly ITestOutputHelper _output; private readonly Patient _patient; private readonly LongTimeTask _task; public PatientShould(ITestOutputHelper output,LongTimeFixture fixture) { this._output = output; _patient = new Patient(); //_task = new LongTimeTask(); _task = fixture.Task;//获取方法 } }
[Theory] [InlineData(1,2,3)] [InlineData(2,2,4)] [InlineData(3,3,6)] public void ShouldAddEquals(int operand1,int operand2,int expected) { //Arrange var sut = new Calculator(); //sut-system under test //Act var result = sut.Add(operand1, operand2); //Assert Assert.Equal(expected, result); }
using System.Collections.Generic; namespace DemoTest { public class CalculatorTestData { private static readonly List<object[]> Data = new List<object[]> { new object[]{ 1,2,3}, new object[]{ 1,3,4}, new object[]{ 2,4,6}, new object[]{ 0,1,1}, }; public static IEnumerable<object[]> TestData => Data; } }
/// <summary> /// 数据共享-MemberData /// </summary> /// <param name="operand1"></param> /// <param name="operand2"></param> /// <param name="expected"></param> [Theory] [MemberData(nameof(CalculatorTestData.TestData),MemberType =typeof(CalculatorTestData))] public void ShouldAddEquals2(int operand1, int operand2, int expected) { //Arrange var sut = new Calculator(); //sut-system under test //Act var result = sut.Add(operand1, operand2); //Assert Assert.Equal(expected, result); }
using System.Collections.Generic; using System.IO; using System.Linq; namespace DemoTest.Data { /// <summary> /// 读取文件并返回数据集合 /// </summary> public class CalculatorCsvData { public static IEnumerable<object[]> TestData { get { //把csv文件中的数据读出来,转换 string[] csvLines = File.ReadAllLines("Data\\TestData.csv"); var testCases = new List<object[]>(); foreach (var csvLine in csvLines) { IEnumerable<int> values = csvLine.Trim().Split(',').Select(int.Parse); object[] testCase = values.Cast<object>().ToArray(); testCases.Add(testCase); } return testCases; } } } }
1,2,3 1,3,4 2,4,6 0,1,1
/// <summary> /// 数据共享-MemberData-外部数据 /// </summary> /// <param name="operand1"></param> /// <param name="operand2"></param> /// <param name="expected"></param> [Theory] [MemberData(nameof(CalculatorCsvData.TestData), MemberType = typeof(CalculatorCsvData))] public void ShouldAddEquals3(int operand1, int operand2, int expected) { //Arrange var sut = new Calculator(); //sut-system under test //Act var result = sut.Add(operand1, operand2); //Assert Assert.Equal(expected, result); }
using System.Collections.Generic; using System.Reflection; using Xunit.Sdk; namespace DemoTest.Data { public class CalculatorDataAttribute : DataAttribute { public override IEnumerable<object[]> GetData(MethodInfo testMethod) { yield return new object[] { 0, 100, 100 }; yield return new object[] { 1, 99, 100 }; yield return new object[] { 2, 98, 100 }; yield return new object[] { 3, 97, 100 }; } } }
/// <summary> /// 数据共享-自定义特性继承自DataAttribute /// </summary> /// <param name="operand1"></param> /// <param name="operand2"></param> /// <param name="expected"></param> [Theory] [CalculatorDataAttribute] public void ShouldAddEquals4(int operand1, int operand2, int expected) { //Arrange var sut = new Calculator(); //sut-system under test //Act var result = sut.Add(operand1, operand2); //Assert Assert.Equal(expected, result); }
源码:https://gitee.com/Alexander360/LearnXUnit