0


.net、C#单元测试xUnit

xUnit单元测试

测试的分类

  • 单元测试:对某个类或者某个方法进行测试
  • 集成测试:可使用Web资源、数据库数据进行测试
  • 皮下测试:在Web中对controller下的节点测试
  • UI测试:对界面的功能进行测试程序员主要关注单元测试集成测试

xUnit

xUnit是一个可对.net进行测试的框架,支持.Net Framework、.Net Core、.Net Standard、UWP、Xamarin。

  • 支持多平台
  • 并行测试
  • 数据驱动测试
  • 可扩展

测试一般针对Public方法进行测试,如果是私有方法需要改变修饰符才能进行测试。同时测试项目需添加对被测项目的引用,同时测试项目需要引入xUnit框架库。

最简单的测试

  1. 创建一个.net core类库:Demo,添加一个Calculator类namespaceDemo{publicclassCalculator{publicintAdd(int x,int y){return x + y;}}}
  2. 在同一解决方案,创建一个xUnit测试项目:DemoTest命名规则:一般是项目名+Test命名测试项目。创建一个类:CalculatorTests:publicclassCalculatorTests{[Fact]publicvoidShouldAddEquals5()//注意命名规范{//Arrange var sut =newCalculator();//sut-system under test,通用命名//Actvar result = sut.Add(3,2);//Assert Assert.Equal(5, result);}}
  3. 运行测试,s自带的测试资源管理器,找到测试项目,选择运行

测试的三个阶段

  • Arrange: 在这里做一些准备。例如创建对象实例,数据,输入等。
  • Act: 在这里执行生产代码并返回结果。例如调用方法或者设置属性。
  • Assert:在这里检查结果,会产生测试通过或者失败两种结果。

Assert详解

xUnit提供了以下类型的Assert
类型行为boolTrue/Falsestring是否相等、空、以什么开始/结束、是否包含、是否匹配正则数值是否相等、是否在范围内、浮点的精度集合内容是否相等、是否包含、是否包含某种条件的元素、每个元素是否满足条件事件自定义事件、.net事件Object是否为某种类型、是否继承某类型

实例

创建一个类库

publicclassPatient:Person,INotifyPropertyChanged{publicPatient(){
            IsNew =true;
            BloodSugar =4.900003f;
            History =newList<string>();//throw new InvalidOperationException("not able to create"); 测试异常使用}publicstring FullName =>$"{FirstName}{LastName}";publicint HeartBeatRate {get;set;}publicbool IsNew {get;set;}publicfloat BloodSugar {get;set;}publicList<string> History {get;set;}/// 事件publiceventEventHandler<EventArgs> PatientSlept;publicvoidOnPatientSleep(){
            PatientSlept?.Invoke(this, EventArgs.Empty);}publicvoidSleep(){OnPatientSleep();}publicvoidIncreaseHeartBeatRate(){
            HeartBeatRate =CalculateHeartBeatRate()+2;OnPropertyChanged(nameof(HeartBeatRate));}privateintCalculateHeartBeatRate(){var random =newRandom();return random.Next(1,100);}publiceventPropertyChangedEventHandler PropertyChanged;protectedvirtualvoidOnPropertyChanged([CallerMemberName]string propertyName =null){
            PropertyChanged?.Invoke(this,newPropertyChangedEventArgs(propertyName));}}

Bool类型测试

[Fact]//必须有这个特性publicvoidBeNewWhenCreated(){// Arrangevar patient =newPatient();// Actvar result = patient.IsNew;// Assert
    Assert.True(result);}

String测试

[Fact]publicvoidHaveCorrectFullName(){var patient =newPatient();
    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]publicvoidHaveDefaultBloodSugarWhenCreated(){var p =newPatient();var bloodSugar = p.BloodSugar;
    Assert.Equal(4.9f, bloodSugar,5);//判断是否相等,最后一个是精度,很重要
    Assert.InRange(bloodSugar,3.9,6.1);//判断是否在某一范围内}

空值判断

[Fact]publicvoidHaveNoNameWhenCreated(){var p =newPatient();
    Assert.Null(p.FirstName);
    Assert.NotNull(_patient);}

集合测试

[Fact]publicvoidHaveHadAColdBefore(){//Arrangevar _patient =newPatient();//Actvar diseases =newList<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);}

object测试

[Fact]publicvoidBeAPerson(){var p =newPatient();var p2 =newPatient();
    Assert.IsNotType<Person>(p);//测试对象是否相等,注意这里为false
    Assert.IsType<Patient>(p);

    Assert.IsAssignableFrom<Person>(p);//判断对象是否继承自Person,true//判断是否为同一个实例
    Assert.NotSame(p, p2);//Assert.Same(p, p2);}

异常测试

[Fact]publicvoidThrowException(){var p =newPatient();//判断是否返回指定类型的异常var ex = Assert.Throws<InvalidOperationException>(()=>{ p.NotAllowed();});//判断异常信息是否相等
    Assert.Equal("not able to create", ex.Message);}

判断是否触发事件

[Fact]publicvoidRaizeSleepEvent(){var p =newPatient();
    Assert.Raises<EventArgs>(
        handler=>p.PatientSlept+=handler,
        handler=>p.PatientSlept -= handler,()=> p.Sleep());}

判断属性改变是否触发事件

[Fact]publicvoidRaisePropertyChangedEvent(){var p =newPatient();
    Assert.PropertyChanged(p,nameof(p.HeartBeatRate),()=> p.IncreaseHeartBeatRate());}

分组

使用trait特性,对测试进行分组:[Trait(“GroupName”,“Name”)] 可以作用于方法级和Class级别。相同的分组使用相同的特性。

[Fact][Trait("Category","New")]//凡是使用这个特性且组名一样,则分到一个组中publicvoidBeNewWhenCreated(){...}

忽略测试

在测试方法上加上特性

[Fact(Skip="不跑这个测试")]

自定义输出内容

使用ITestOutputHelper可以自定义在测试时的输出内容

publicclassPatientShould:IClassFixture<LongTimeFixture>,IDisposable{privatereadonlyITestOutputHelper _output;publicPatientShould(ITestOutputHelper output,LongTimeFixture fixture){this._output = output;}[Fact]publicvoidBeNewWhenCreated(){
        _output.WriteLine("第一个测试");}}

常用技巧

  • 减少new对象,减少new对象,可以在构造函数中new,在方法中使用。
  • 测试类实现IDispose接口,测试完释放资源,注意每个测试结束后都会调用Dispose方法。

共享上下文

某个方法需要执行很长时间,而在构造函数中new时,每个测试跑的时候都会new对象或者执行方法,这是导致测试很慢。解决方法:

模拟运行长时间的任务

publicclassLongTimeTask{publicLongTimeTask(){
        Thread.Sleep(2000);}}

相同测试类

  1. 创建一个类:
publicclassLongTimeFixture{publicLongTimeTask Task {get;}publicLongTimeFixture(){
            Task =newLongTimeTask();}}}
  1. 测试类实现IClassFixture<LongTimeFixture>接口,并在构造函数中使用依赖注入的方式获取方法
publicclassPatientShould:IClassFixture<LongTimeFixture>,IDisposable{privatereadonlyPatient _patient;privatereadonlyLongTimeTask _task;publicPatientShould(ITestOutputHelper output,LongTimeFixture fixture){this._output = output;
        _task = fixture.Task;//获取方法}}//这样的话其实只有一个LongTimeTask实例,所以要保证该实例不能有副作用

不同测试类

如果多个测试类都要用到相同的耗时任务,则可以这样用

  1. 添加一个类
[CollectionDefinition("Lone Time Task Collection")]publicclassTaskCollection:ICollectionFixture<LongTimeFixture>{}
  1. 在使用的类上加上[CollectionDefinition("Lone Time Task Collection")]注意里面的字符串要相同
[Collection("Lone Time Task Collection")]publicclassAAAShould:IClassFixture<LongTimeFixture>,IDisposable{privatereadonlyLongTimeTask _task;publicPatientShould(LongTimeFixture fixture){
        _task = fixture.Task;//获取方法}}[Collection("Lone Time Task Collection")]publicclassBBBShould:IClassFixture<LongTimeFixture>,IDisposable{privatereadonlyLongTimeTask _task;publicBBBShould(LongTimeFixture fixture){
        _task = fixture.Task;//获取方法}}//此时这两个类中测试方法都会共享一个LongTimeFixture实例

数据共享

1.[Theory]

可以写有构造参数的测试方法,使用InlineData传递数据

[Theory][InlineData(1,2,3)][InlineData(2,2,4)][InlineData(3,3,6)]publicvoidShouldAddEquals(int operand1,int operand2,int expected){//Arrangevar sut =newCalculator();//sut-system under test//Actvar result = sut.Add(operand1, operand2);//Assert
    Assert.Equal(expected, result);}

2.[MemberData]

可以在多个测试中使用

  1. 创建一个类
publicclassCalculatorTestData{privatestaticreadonlyList<object[]> Data =newList<object[]>{newobject[]{1,2,3},newobject[]{1,3,4},newobject[]{2,4,6},newobject[]{0,1,1},};publicstaticIEnumerable<object[]> TestData => Data;}
  1. 使用MemberData
[Theory][MemberData(nameof(CalculatorTestData.TestData),MemberType =typeof(CalculatorTestData))]publicvoidShouldAddEquals2(int operand1,int operand2,int expected){//Arrangevar sut =newCalculator();//sut-system under test//Actvar result = sut.Add(operand1, operand2);//Assert
    Assert.Equal(expected, result);}

3.使用外部数据

  1. 读取外部集合类
//    读取文件并返回数据集合 必须是IEnumerablepublicclassCalculatorCsvData{publicstaticIEnumerable<object[]> TestData
        {get{//把csv文件中的数据读出来,转换string[] csvLines = File.ReadAllLines("Data\\TestData.csv");var testCases =newList<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. 使用
[Theory][MemberData(nameof(CalculatorCsvData.TestData), MemberType =typeof(CalculatorCsvData))]publicvoidShouldAddEquals3(int operand1,int operand2,int expected){//Arrangevar sut =newCalculator();//sut-system under test//Actvar result = sut.Add(operand1, operand2);//Assert
    Assert.Equal(expected, result);}

4.DataAttribute

  1. 自定义特性
publicclassCalculatorDataAttribute:DataAttribute{publicoverrideIEnumerable<object[]>GetData(MethodInfo testMethod){yieldreturnnewobject[]{0,100,100};yieldreturnnewobject[]{1,99,100};yieldreturnnewobject[]{2,98,100};yieldreturnnewobject[]{3,97,100};}}
  1. 使用
[Theory][CalculatorDataAttribute]publicvoidShouldAddEquals4(int operand1,int operand2,int expected){//Arrangevar sut =newCalculator();//sut-system under test//Actvar result = sut.Add(operand1, operand2);//Assert
    Assert.Equal(expected, result);}
标签: 单元测试 .net c#

本文转载自: https://blog.csdn.net/weixin_44064908/article/details/128138551
版权归原作者 步、步、为营 所有, 如有侵权,请联系我们删除。

“.net、C#单元测试xUnit”的评论:

还没有评论