zoukankan      html  css  js  c++  java
  • 使用xUnit为.net core程序进行单元测试 -- Assert

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

    Assert

    Assert做什么?Assert基于代码的返回值、对象的最终状态、事件是否发生等情况来评估测试的结果。Assert的结果可能是Pass或者Fail。如果所有的asserts都pass了,那么整个测试就pass了;如果有任何assert fail了,那么测试就fail了。

    xUnit提供了以下类型的Assert:

    • boolean:True/False
    • String:相等/不等,是否为空,以..开始/结束,是否包含子字符串,匹配正则表达式
    • 数值型:相等/不等,是否在某个范围内,浮点的精度
    • Collection:内容是否相等,是否包含某个元素,是否包含满足某种条件(predicate)的元素,是否所有的元素都满足某个assert
    • Raised events:Custom events,Framework events(例如:PropertyChanged)
    • Object Type:是否是某种类型,是否某种类型或继承与某种类型

    一个test里应该有多少个asserts?

    一种建议的做法是,每个test方法里面只有一个assert。

    而还有一种建议就是,每个test里面可以有多个asserts,只要这些asserts都是针对同一个行为就行。

    第一个Assert

    目标类:

    复制代码
    复制代码
        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);
            }
        }
    复制代码
    复制代码

    测试类:

    复制代码
    复制代码
        public class PatientShould
        {
            [Fact]
            public void HaveHeartBeatWhenNew()
            {
                var patient = new Patient();
    
                Assert.True(patient.IsNew);
            }
        }
    复制代码
    复制代码

    运行测试:

     

    结果符合预期,测试通过。

    改为Assert.False()的话:

    测试Fail。

    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));
            }
        }
    }
    复制代码

    添加一个Test:

    复制代码
            [Fact]
            public void RaisePropertyChangedEvent()
            {
                var p = new Patient();
                Assert.PropertyChanged(p, "BloodSugar", () => p.HaveDinner());
            }
    复制代码

    针对INotifyPropertyChanged, 可以使用Assert.PropertyChanged(..) 这个专用的方法来断定PropertyChanged的Event是否被触发了.

    Build, Run Tests: Pass.

    到目前为止, 介绍的都是入门级的内容.

    接下来要介绍的是稍微进阶一点的内容了.

    来源:https://www.cnblogs.com/cgzl/p/8287588.html

  • 相关阅读:
    PAT 1097. Deduplication on a Linked List (链表)
    PAT 1096. Consecutive Factors
    PAT 1095. Cars on Campus
    PAT 1094. The Largest Generation (层级遍历)
    PAT 1093. Count PAT's
    PAT 1092. To Buy or Not to Buy
    PAT 1091. Acute Stroke (bfs)
    CSS:word-wrap/overflow/transition
    node-webkit中的requirejs报错问题:path must be a string error in Require.js
    script加载之defer和async
  • 原文地址:https://www.cnblogs.com/frank0812/p/13442252.html
Copyright © 2011-2022 走看看