使用xUnit为.net core程序进行单元测试(2)

简介: 第一部分: http://www.cnblogs.com/cgzl/p/8283610.html 下面有一点点内容是重叠的.... String Assert 测试string是否相等: [Fact] public void CalculateFullN...

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

下面有一点点内容是重叠的....

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.

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

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

下面是我的关于ASP.NET Core Web API相关技术的公众号--草根专栏:

目录
相关文章
|
1天前
|
安全 Linux 网络安全
Kali渗透测试:远程控制程序基础
Kali渗透测试:远程控制程序基础
Kali渗透测试:远程控制程序基础
|
1天前
|
安全 Java Linux
Kali渗透测试:通过Web应用程序实现远程控制
Kali渗透测试:通过Web应用程序实现远程控制
25 0
|
1月前
|
Ubuntu 持续交付 API
如何使用 dotnet pack 打包 .NET 跨平台程序集?
`dotnet pack` 是 .NET Core 的 NuGet 包打包工具,用于将代码打包成 NuGet 包。通过命令 `dotnet pack` 可生成 `.nupkg` 文件。使用 `--include-symbols` 和 `--include-source` 选项可分别创建包含调试符号和源文件的包。默认情况下,`dotnet pack` 会先构建项目,可通过 `--no-build` 跳过构建。此外,还可以使用 `--output` 指定输出目录、`-c` 设置配置等。示例展示了创建类库项目并打包的过程。更多详情及命令选项,请参考官方文档。
93 11
|
1月前
|
存储 运维
.NET开发必备技巧:使用Visual Studio分析.NET Dump,快速查找程序内存泄漏问题!
.NET开发必备技巧:使用Visual Studio分析.NET Dump,快速查找程序内存泄漏问题!
|
1月前
|
自然语言处理 C# 图形学
使用dnSpyEx对.NET Core程序集进行反编译、编辑和调试
使用dnSpyEx对.NET Core程序集进行反编译、编辑和调试
|
2月前
|
jenkins 测试技术 持续交付
解锁.NET项目高效秘籍:从理论迷雾到实践巅峰,持续集成与自动化测试如何悄然改变游戏规则?
【8月更文挑战第28天】在软件开发领域,持续集成(CI)与自动化测试已成为提升效率和质量的关键工具。尤其在.NET项目中,二者的结合能显著提高开发速度并保证软件稳定性。本文将从理论到实践,详细介绍CI与自动化测试的重要性,并以ASP.NET Core Web API项目为例,演示如何使用Jenkins和NUnit实现自动化构建与测试。每次代码提交后,Jenkins自动触发构建流程,通过编译和运行NUnit测试确保代码质量。这种方式不仅节省了时间,还能快速发现并解决问题,推动.NET项目开发迈向更高水平。
41 8
|
2月前
|
测试技术 API 开发者
.NET单元测试框架大比拼:MSTest、xUnit与NUnit的实战较量与选择指南
【8月更文挑战第28天】单元测试是软件开发中不可或缺的一环,它能够确保代码的质量和稳定性。在.NET生态系统中,MSTest、xUnit和NUnit是最为流行的单元测试框架。本文将对这三种测试框架进行全面解析,并通过示例代码展示它们的基本用法和特点。
93 7
|
2月前
|
Java Spring UED
Spring框架的异常处理秘籍:打造不败之身的应用!
【8月更文挑战第31天】在软件开发中,异常处理对应用的稳定性和健壮性至关重要。Spring框架提供了一套完善的异常处理机制,包括使用`@ExceptionHandler`注解和配置`@ControllerAdvice`。本文将详细介绍这两种方式,并通过示例代码展示其具体应用。`@ExceptionHandler`可用于控制器类中的方法,处理特定异常;而`@ControllerAdvice`则允许定义全局异常处理器,捕获多个控制器中的异常。
39 0
|
2月前
|
API 开发者 Java
API 版本控制不再难!Spring 框架带你玩转多样化的版本管理策略,轻松应对升级挑战!
【8月更文挑战第31天】在开发RESTful服务时,为解决向后兼容性问题,常需进行API版本控制。本文以Spring框架为例,探讨四种版本控制策略:URL版本控制、请求头版本控制、查询参数版本控制及媒体类型版本控制,并提供示例代码。此外,还介绍了通过自定义注解与过滤器实现更灵活的版本控制方案,帮助开发者根据项目需求选择最适合的方法,确保API演化的管理和客户端使用的稳定与兼容。
93 0
|
2月前
|
安全 数据安全/隐私保护 架构师
用Vaadin打造坚不可摧的企业级应用:安全性考虑全解析
【8月更文挑战第31天】韩林是某金融科技公司的架构师,负责构建安全的企业级应用。在众多Web框架中,他选择了简化UI设计并内置多项安全特性的Vaadin。韩林在其技术博客中分享了使用Vaadin时的安全考虑与实现方法,包括数据加密、SSL/TLS保护、结合Spring Security的用户认证、XSS防护、CSRF防御及事务性UI更新机制。他强调,虽然Vaadin提供了丰富的安全功能,但还需根据具体需求进行调整和增强。通过合理设计,可以构建高效且安全的企业级Web应用。
37 0