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

    一. 导读

    为什么要编写自动化测试程序(Automated Tests)?

    • 可以频繁的进行测试

    • 可以在任何时间进行测试,也可以按计划定时进行,例如:可以在半夜进行自动测试。

    • 肯定比人工测试要快。

    • 可以更快速的发现错误。

    • 基本上是非常可靠的。

    • 测试代码与生产代码紧密结合。

    • 使得开发团队更具有幸福感!

    自动化测试的分类:

    640?wx_fmt=png&wxfrom=5&wx_lazy=1

    纵轴表示测试的深度,也就是说测试的细致程度。

    横轴则表示测试的覆盖程度。

    • Unit Test 单元测试, 它可以测试一个类,或者一个类的某个功能,它具有很好的深度,但是对整个应用来说它不具备很好的覆盖面。

    • Integration Test 集成测试,它没有单元测试那么细致,但是具有相对较好的测试覆盖面。例如它可以测试功能的组合,以及像数据库或文件系统这样的外部资源等。

    • Subcutaneous Test 皮下测试,这种测试作用于UI层的下面一层,这也意味着它对整个应用来说有很好的覆盖率,但是深度欠佳。那一个MVC结构的应用来说,它就是针对刚好在Controller下面一层的测试,对于Web service来说,它就是对节点下面那层的测试。

    • UI测试,它的测试覆盖面很广,直接从UI层面进行测试,但是深度欠佳。

    从速度来看 单元是最快的,而UI测试是最慢的。

    从脆弱性来看 UI测试是最差的,程序修改后极有可能需要修改测试代码,而单元测试是最好的。

    是测试行为还是测试私有方法(private method)?

    public void IncreaseHeartBeatRate()

            {

                HeartBeatRate = CalculateHeartBeatRate() + 2;

            }

            private int CalculateHeartBeatRate()

            {

                var random = new Random();

                return random.Next(1, 100);

            }

    大多数情况下单元测试都应该是针对类的行为进行测试的,也就是public方法。当然也纯在不同的观点。

    如果想要对private方法进行测试的话,是有很多缺点的:

    • 首先需要修改方法的访问限制需要从private改为public,这就破坏了面向对象的封装性。

    • 再者,这其实测试的是类的具体实现细节,而不是类的行为。如果我们想要对类的内部进行重构的话,就会破坏测试,导致测试也必须重构。如果必须对private方法进行测试,那么首先建议您把private修饰符改成internal,然后修改该项目(project)的AssemblyInfo.cs,它在项目的Debug或者Release文件夹下。代码如下:

    [assembly: InternalsVisibleTo("Hospital.Tests")]

    这表示Hospital.Tests这个测试项目可以访问该项目生产代码(production code)的internal方法。

    测试的三个阶段 AAA

    0?wx_fmt=png

    • Arrange,这里做一些先决的设定。例如创建对象实例,数据,输入等等。

    • Act,在这里执行生产代码并返回结果。例如调用方法,或者设置属性(Properties)。

    • Assert,在这里检查结果。测试通过或者失败。

    xUnit.net

    官网:https://xunit.github.io/

    0?wx_fmt=png

    xUnit是一个测试框架,可以针对.net/core进行测试。

    测试项目需引用被项目从而对其进行测试,测试项目同时需要引用xUnit库。测试编写好后,用Test Runner来运行测试。Test Runner可以读取测试代码,并且会知道我们所使用的测试框架,然后执行,并显示结果。目前可用的Test Runner包括vs自带的Test Explorer,或者dotnet core命令行,以及第三方工具,例如resharper等等。

    xUnit支持的平台:

    .net full, .net core, .net standard, uwp, xamarin.

    xUnit的例子:

    [Fact]

            public void TestIncreaseHeartBeatRate()

            {

                var patient = new Patient(); // Arrange

                patient.IncreaseHeartBeatRate(); // Act

                Assert.InRange(patient.HeartBeatRate, 40, 100); // Assert

            }

    安装配置xUnit.net

    a.使用Visual Studio 2017

    首先建立一个C# library项目,叫Hospital(下面部分截图有个拼写错误,应该是Hospital),然后建立一个xUnit Test项目,叫Hospital.Tests:

     0?wx_fmt=png

    可以看到Hospital.Tests已经包含里这几个库:

    0?wx_fmt=png

    然后为Hospital.Tests添加到Hospital项目的引用。

    b.使用.net core 命令行

    首先把刚才建立的Hospital.Tests项目移除(目录需要手动删除).

    然后打开项目位置:

    0?wx_fmt=png

    按住shift打开命令行:

    0?wx_fmt=png

    用命令行创建项目:

    0?wx_fmt=png

    创建 Hospital.Tests目录,进入目录,使用命令dotnet new xunit创建xUnit单元测试项目。

    添加项目的引用:

    0?wx_fmt=png

    最后添加项目到解决方案:

    0?wx_fmt=png

    回到VS界面,提示重新加载:

    0?wx_fmt=png

    确认后,VS中解决方案结构如:

    0?wx_fmt=png

    做第一个测试

    对测试项目的文件名进行一些重构,编写以下代码,并进行Build:

    0?wx_fmt=png

    从Test Explorer我们可以看到一个待测试的项目。

    在这里,我们可以对测试项目进行分组和排序,如图:

    0?wx_fmt=png

    想要运行所有的测试,就点击上面的Run All按钮。如果像运行单个测试,那么右击选择Run Selected Tests:

    0?wx_fmt=png

    运行后,可以看到结果,Passed:

    0?wx_fmt=png

    我们同样可以通过命令行来进行测试:

    进入到Tests目录,执行 dotnet test命令,所有的测试都会被发现,然后被执行:

    0?wx_fmt=png

    因为我们并没有在测试方法中写任何的Assert,所以测试肯定是通过的,但这个测试也是个无效的测试。

    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);

            }

        }

    运行测试:

     0?wx_fmt=png

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

    改为Assert.False()的话:

    0?wx_fmt=png

    测试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:

    0?wx_fmt=png

    同样改一下Patient类(别忘了Build一下),让结果失败:

    0?wx_fmt=png

    从失败信息可以看到期待值和实际值。

    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:

    0?wx_fmt=png

    忽略大小写 ignoreCase

    string默认的Assert是区分大小写的,这样就会失败:

    0?wx_fmt=png

    可以为这些方法添加一个参数ignoreCase设置为true,就会忽略大小写:

    0?wx_fmt=png

    包含子字符串 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:

    0?wx_fmt=png

    可以看到期待的Range和实际的值,这样很好。如果你使用Assert.True(xx >= 5 && xx <= 6)的话,错误信息只能显示True或者False。

    因为HaveDinner方法里,表达式的分母应该是1000,修改后,Build,Run,测试Pass。

  • 相关阅读:
    简时——Beta冲刺 Day04
    简时——Beta冲刺 Day03
    简时——Beta冲刺 Day02
    SE_WorkX_提问回顾与个人总结
    通过SQL注入获得网站后台用户密码
    缓冲区溢出攻击与防范
    Pytorch_Part7_模型使用
    Pytorch_Part5_迭代训练
    Pytorch_Part2_数据模块
    Pytorch_Part1_简介&张量
  • 原文地址:https://www.cnblogs.com/dmyao/p/12718356.html
Copyright © 2011-2022 走看看