namespace Demo
{public class Calculator{public int Add(int x,int y){return x + y;}}
}
在同一解决方案,创建一个xUnit测试项目:DemoTest
命名规则:一般是项目名+Test命名测试项目。创建一个类:CalculatorTests:
public class CalculatorTests
{[Fact]public void ShouldAddEquals5() //注意命名规范{//Arrange var sut = new Calculator(); //sut-system under test,通用命名//Actvar result = sut.Add(3, 2);//AssertAssert.Equal(5, result);}
}
运行测试,s自带的测试资源管理器,找到测试项目,选择运行
测试的三个阶段
Arrange: 在这里做一些准备。例如创建对象实例,数据,输入等。
Act: 在这里执行生产代码并返回结果。例如调用方法或者设置属性。
Assert:在这里检查结果,会产生测试通过或者失败两种结果。
Assert详解
xUnit提供了以下类型的Assert
类型
行为
bool
True/False
string
是否相等、空、以什么开始/结束、是否包含、是否匹配正则
数值
是否相等、是否在范围内、浮点的精度
集合
内容是否相等、是否包含、是否包含某种条件的元素、每个元素是否满足条件
事件
自定义事件、.net事件
Object
是否为某种类型、是否继承某类型
实例
创建一个类库
public class Patient : Person, INotifyPropertyChanged{public Patient(){IsNew = true;BloodSugar = 4.900003f;History = new List();//throw new InvalidOperationException("not able to create"); 测试异常使用}public string FullName => $"{FirstName} {LastName}";public int HeartBeatRate { get; set; }public bool IsNew { get; set; }public float BloodSugar { get; set; }public List History { get; set; }/// 事件public event EventHandler PatientSlept;public void OnPatientSleep(){PatientSlept?.Invoke(this, EventArgs.Empty);}public void Sleep(){OnPatientSleep();}public void IncreaseHeartBeatRate(){HeartBeatRate = CalculateHeartBeatRate() + 2;OnPropertyChanged(nameof(HeartBeatRate));}private int CalculateHeartBeatRate(){var random = new Random();return random.Next(1, 100);}public event PropertyChangedEventHandler PropertyChanged;protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null){PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));}}
Bool类型测试
[Fact] //必须有这个特性
public void BeNewWhenCreated()
{// Arrangevar patient = new Patient();// Actvar result = patient.IsNew;// AssertAssert.True(result);
}
[Fact]
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()
{//Arrangevar _patient = new Patient();//Actvar diseases = new List{"感冒","发烧","水痘","腹泻"};_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);}
object测试
[Fact]
public void BeAPerson()
{var p = new Patient();var p2 = new Patient();Assert.IsNotType(p); //测试对象是否相等,注意这里为falseAssert.IsType(p);Assert.IsAssignableFrom(p);//判断对象是否继承自Person,true//判断是否为同一个实例Assert.NotSame(p, p2);//Assert.Same(p, p2);}
异常测试
[Fact]
public void ThrowException()
{var p = new Patient();//判断是否返回指定类型的异常var ex = Assert.Throws(()=> { p.NotAllowed(); });//判断异常信息是否相等Assert.Equal("not able to create", ex.Message);
}
判断是否触发事件
[Fact]
public void RaizeSleepEvent()
{var p = new Patient();Assert.Raises(handler=>p.PatientSlept+=handler,handler=>p.PatientSlept -= handler,() => p.Sleep());
}
判断属性改变是否触发事件
[Fact]
public void RaisePropertyChangedEvent()
{var p = new Patient();Assert.PropertyChanged(p, nameof(p.HeartBeatRate),() => p.IncreaseHeartBeatRate());
}
public class LongTimeTask
{public LongTimeTask(){Thread.Sleep(2000);}
}
相同测试类
创建一个类:
public class LongTimeFixture{public LongTimeTask Task { get; }public LongTimeFixture(){Task = new LongTimeTask();}}
}
测试类实现IClassFixture接口,并在构造函数中使用依赖注入的方式获取方法
public class PatientShould:IClassFixture,IDisposable
{private readonly Patient _patient;private readonly LongTimeTask _task;public PatientShould(ITestOutputHelper output,LongTimeFixture fixture){this._output = output;_task = fixture.Task;//获取方法}
}
//这样的话其实只有一个LongTimeTask实例,所以要保证该实例不能有副作用
不同测试类
如果多个测试类都要用到相同的耗时任务,则可以这样用
添加一个类
[CollectionDefinition("Lone Time Task Collection")]public class TaskCollection:ICollectionFixture{}
在使用的类上加上[CollectionDefinition("Lone Time Task Collection")]注意里面的字符串要相同
[Collection("Lone Time Task Collection")]
public class AAAShould:IClassFixture,IDisposable
{private readonly LongTimeTask _task;public PatientShould(LongTimeFixture fixture){_task = fixture.Task;//获取方法}
}
[Collection("Lone Time Task Collection")]
public class BBBShould:IClassFixture,IDisposable
{private readonly LongTimeTask _task;public BBBShould(LongTimeFixture fixture){_task = fixture.Task;//获取方法}
}
//此时这两个类中测试方法都会共享一个LongTimeFixture实例
数据共享
1.[Theory]
可以写有构造参数的测试方法,使用InlineData传递数据
[Theory]
[InlineData(1,2,3)]
[InlineData(2,2,4)]
[InlineData(3,3,6)]
public void ShouldAddEquals(int operand1,int operand2,int expected)
{//Arrangevar sut = new Calculator(); //sut-system under test//Actvar result = sut.Add(operand1, operand2);//AssertAssert.Equal(expected, result);
}
2.[MemberData]
可以在多个测试中使用
创建一个类
public class CalculatorTestData{private static readonly List
使用MemberData
[Theory]
[MemberData(nameof(CalculatorTestData.TestData),MemberType =typeof(CalculatorTestData))]
public void ShouldAddEquals2(int operand1, int operand2, int expected)
{//Arrangevar sut = new Calculator(); //sut-system under test//Actvar result = sut.Add(operand1, operand2);//AssertAssert.Equal(expected, result);
}
3.使用外部数据
读取外部集合类
// 读取文件并返回数据集合 必须是IEnumerable
public class CalculatorCsvData
{public static IEnumerable TestData{get{//把csv文件中的数据读出来,转换string[] csvLines = File.ReadAllLines("Data\\TestData.csv");var testCases = new List();foreach (var csvLine in csvLines){IEnumerable values = csvLine.Trim().Split(',').Select(int.Parse);object[] testCase = values.Cast().ToArray();testCases.Add(testCase);}return testCases;}}}
使用
[Theory]
[MemberData(nameof(CalculatorCsvData.TestData), MemberType = typeof(CalculatorCsvData))]
public void ShouldAddEquals3(int operand1, int operand2, int expected)
{//Arrangevar sut = new Calculator(); //sut-system under test//Actvar result = sut.Add(operand1, operand2);//AssertAssert.Equal(expected, result);
}
4.DataAttribute
自定义特性
public class CalculatorDataAttribute : DataAttribute{public override IEnumerable 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 };}}
使用
[Theory]
[CalculatorDataAttribute]
public void ShouldAddEquals4(int operand1, int operand2, int expected)
{//Arrangevar sut = new Calculator(); //sut-system under test//Actvar result = sut.Add(operand1, operand2);//AssertAssert.Equal(expected, result);
}