• 使用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));
            }
        }
    }
    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.

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

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

  • 相关阅读:
    Difference between Nested & Correlated Subqueries
    Oracle Btree、位图、全文索引三大索引性能比较及优缺点汇总(转载)
    subquery unnesting、Subquery unnesting and View Merge
    MySQL中如何定义外键[转]
    索引1
    创建索引和索引类型
    UpdatePanel的用法详解
    索引2
    [HTTP]GET 和POST的区别
    [转]解决silverlight引用中文字体的问题
  • 原文地址:https://www.cnblogs.com/cgzl/p/8287588.html
Copyright © 2020-2023  润新知