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

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

第2部分: http://www.cnblogs.com/cgzl/p/8287588.html

第3部分: http://www.cnblogs.com/cgzl/p/8438019.html

请使用这个项目的代码: https://pan.baidu.com/s/1i7d8z2H

数据驱动的测试

打开PlayerCharacterShould.cs

添加几个Fact测试方法:

        [Fact]
        public void TakeZeroDamage()
        {
            _sut.TakeDamage(0);
            Assert.Equal(100, _sut.Health);
        }

        [Fact]
        public void TakeSmallDamage()
        {
            _sut.TakeDamage(1);
            Assert.Equal(99, _sut.Health);
        }

        [Fact]
        public void TakeMediumDamage()
        {
            _sut.TakeDamage(50);
            Assert.Equal(50, _sut.Health);
        }

        [Fact]
        public void TakeMinimum1Damage()
        {
            _sut.TakeDamage(101);
            Assert.Equal(1, _sut.Health);
        }

Build, Run tests. 都Pass了.

仔细看下这4个方法, 他们其实是做了同样的事情, 只不过输入的数据和期待的结果不同而已.

所以我们应该重构一下这段代码.

Theory:

针对上述情况, 我们就不再使用Fact属性标签了, 而是需要使用Theory.

Theory标签会告诉xUnit, 它下面的测试方法会被执行多次, 而每次执行必须为这个方法提供必要的测试数据.

如何为其添加测试数据呢? 首先要为测试方法添加参数, 使用参数来代替具体的数值:

        [Theory]
        public void TakeDamage(int damage, int expectedHealth)
        {
            _sut.TakeDamage(damage);
            Assert.Equal(expectedHealth, _sut.Health);
        }

然后我们需要告诉xUnit这个测试方法的参数来自哪里.

1. 最简单的办法是使用InlineData属性标签:

        [Theory]
        [InlineData(0, 100)]
        [InlineData(1, 99)]
        [InlineData(50, 50)]
        [InlineData(101, 1)]
        public void TakeDamage(int damage, int expectedHealth)
        {
            _sut.TakeDamage(damage);
            Assert.Equal(expectedHealth, _sut.Health);
        }

上面我添加了四组测试数据, 每对数据按顺序对应测试方法的两个参数. (InlineData的参数类型是params object[])

然后Build, 查看Test Explorer:

会发现这里面多出来了4个测试, 分别对应那4个InlineData.

Run Tests, 都会Pass的.

现在就可以把那四个Fact测试方法删除了.

尽管InlineData使用起来还是很方便, 但是在某些情境下还是灵活性欠佳, 请您查看NonPlayerCharacterShould.cs里面的代码. 取消里面的注释:

namespace Game.Tests
{
    public class NonPlayerCharacterShould
    {
        [Theory]
        [InlineData(0, 100)]
        [InlineData(1, 99)]
        [InlineData(50, 50)]
        [InlineData(101, 1)]
        public void TakeDamage(int damage, int expectedHealth)
        {
            NonPlayerCharacter sut = new NonPlayerCharacter();

            sut.TakeDamage(damage);

            Assert.Equal(expectedHealth, sut.Health);
        }
    }
}

首先Build, Run Tests, 都Pass.

这个Theory的四组参数和上面的是一样的.

2.为了共享这几组测试数据, 可以使用MemberData属性标签, 首先创建一个类InternalHealthDamageTestData.cs:

namespace Game.Tests
{
    public class InternalHealthDamageTestData
    {
        private static readonly List<object[]> Data = new List<object[]>
        {
            new object[] {0, 100},
            new object[] {1, 99},
            new object[] {50, 50},
            new object[] {101, 1}
        };

        public static IEnumerable<object[]> TestData => Data;
    }
}

这里面的数据和之前的那四组数据是一样的.

然后修改NonPlayerCharacterShould里面的代码, 把InlineData都去掉:

namespace Game.Tests
{
    public class NonPlayerCharacterShould
    {
        [Theory]
        [MemberData(nameof(InternalHealthDamageTestData.TestData), MemberType = typeof(InternalHealthDamageTestData))]
        public void TakeDamage(int damage, int expectedHealth)
        {
            NonPlayerCharacter sut = new NonPlayerCharacter();

            sut.TakeDamage(damage);

            Assert.Equal(expectedHealth, sut.Health);
        }
    }
}

这里改成了MemberData, 它的参数很多, 第一个参数是数据提供类的属性名字, 这个属性类型要求是IEnumberable的, 所以这里应该写"TestData", 不过最好还是使用nameof, 这样如果更改了数据类的属性名称, 那么编译时就会报错, 而不会导致测试失败.

然后还需要设置MemberType属性, 表明数据提供类的类型.

Clean Solution, Build, 可以看到还是有4个测试, Run Tests, 都会Pass的.

针对PlayerCharacterShould, 也这样修改. 这样测试数据就得到了共享.

3. 外部数据.

查看一下项目里面的TestData.csv: 里面还是这四组数据:

0, 100
1, 99
50, 50
101, 1

再创建一个类ExternalHealthDamageTestData.cs来取出csv中的数据:

namespace Game.Tests
{
    public class ExternalHealthDamageTestData
    {
        public static IEnumerable<object[]> TestData
        {
            get
            {
                string[] csvLines = File.ReadAllLines("TestData.csv");
                var testCases = new List<object[]>();
                foreach (var csvLine in csvLines)
                {
                    IEnumerable<int> values = csvLine.Split(‘,‘).Select(int.Parse);
                    object[] testCase = values.Cast<object>().ToArray();
                    testCases.Add(testCase);
                }
                return testCases;
            }
        }
    }
}

修改一下NonPlayerCharacterShould和PlayerCharacterShould相关测试方法的属性标签:

namespace Game.Tests
{
    public class NonPlayerCharacterShould
    {
        [Theory]
        [MemberData(nameof(ExternalHealthDamageTestData.TestData), MemberType = typeof(ExternalHealthDamageTestData))]
        public void TakeDamage(int damage, int expectedHealth)
        {
            NonPlayerCharacter sut = new NonPlayerCharacter();

            sut.TakeDamage(damage);

            Assert.Equal(expectedHealth, sut.Health);
        }
    }
}
        [Theory]
        [MemberData(nameof(ExternalHealthDamageTestData.TestData), MemberType = typeof(ExternalHealthDamageTestData))]
        public void TakeDamage(int damage, int expectedHealth)
        {
            _sut.TakeDamage(damage);
            Assert.Equal(expectedHealth, _sut.Health);
        }

Build, 查看Test Explorer:

针对他们中的任意一个类, 只能发现一个相关的测试, 而不是四个测试.

Run Tests的话, 会报错:

它找不到TestData.csv, 这是因为我们需要更改一下csv文件的属性, 把它改成Copy always:

然后选择Rebuild Solution, 这样才能保证csv文件被copy到正确的位置.

再查看Test Explorer:

这时就会看到4组测试了, Run Tests, 都会Pass的.

如果再添加一组数据, 还是需要Rebuild Solution的, 然后新的测试会出现在Test Explorer里面.

4.CustomDataAttribute 自定义数据属性标签.

使用自定义的标签可以把测试数据在test case和class之间共享, 而且会提高测试的可读性.

建立一个类 HealthDamageDataAttribute.cs:

namespace Game.Tests
{
    public class HealthDamageDataAttribute : DataAttribute
    {
        public override IEnumerable<object[]> GetData(MethodInfo testMethod)
        {
            yield return new object[] { 0, 100 };
            yield return new object[] { 1, 99 };
            yield return new object[] { 50, 50 };
            yield return new object[] { 101, 1 };
        }
    }
}

这里需要实现xUnit的DataAttribute这个抽象类.

修改NonPlayerCharacterShould和PlayerCharacterShould的相关方法, 把上面的自定义标签写上去:

namespace Game.Tests
{
    public class NonPlayerCharacterShould
    {
        [Theory]
        [HealthDamageData]
        public void TakeDamage(int damage, int expectedHealth)
        {
            NonPlayerCharacter sut = new NonPlayerCharacter();

            sut.TakeDamage(damage);

            Assert.Equal(expectedHealth, sut.Health);
        }
    }
}

Build, 然后再Test Explorer还是可以看到四组测试, 如果再想添加一组测试, 只需重新Build即可.

测试同样都会Pass的.

同样自定义标签可以整合外部数据, 这个很简单, 您自己来写一下吧.

这个xUnit简介就到此为止了, 想要深入了解的话, 还是看官方文档吧.

原文地址:https://www.cnblogs.com/cgzl/p/8444423.html

时间: 2024-10-14 05:51:27

使用xUnit为.net core程序进行单元测试(4)的相关文章

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

第1部分: http://www.cnblogs.com/cgzl/p/8283610.html 第2部分: http://www.cnblogs.com/cgzl/p/8287588.html 请使用这个项目作为练习的开始: https://pan.baidu.com/s/1ggcGkGb 测试的分组 打开Game.Tests里面的BossEnemyShould.cs, 为HaveCorrectPower方法添加一个Trait属性标签: [Fact] [Trait("Category"

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

第一部分: 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"

好代码是管出来的——.Net Core中的单元测试与代码覆盖率

测试对于软件来说,是保证其质量的一个重要过程,而测试又分为很多种,单元测试.集成测试.系统测试.压力测试等等,不同的测试的测试粒度和测试目标也不同,如单元测试关注每一行代码,集成测试关注的是多个模块是否能正常的协同工作. 当我们在衡量代码好坏时,其中一点就是这些代码是否进行了单元测试,测试的质量.代码覆盖率怎么样?本文将从以下几个方面介绍.Net Core中的单元测试: 单元测试简介 .Net Core中的单元测试框架 使用xUnit.Net对.Net Core应用进行单元测试 创建xUnit.

.NET Core: 在.NET Core中进行单元测试

单元测试能够帮助开发人员确保所开发的模块.类以及类中的方法等的正确性,在项目开发过程中,及时进行单元测试能够避免不必要的BUG以及提高测试效率. 在本文中,我们会分别来学习如何使用MSTest.xUnit以及NUnit这些流行的.NET测试框架来对.NET Core项目进行测试. 一.项目创建 首先,创建一个需要测试的项目.在Visual Studio中创建一个.NET Core 类库项目,名为Calc,项目创建成功后,删除自带的Class1.cs,添加一个类Calculator. Calcul

跨平台部署.NET Core程序

开发环境:Win10 开发工具:Visual Studio 2015 部署环境:centos 7-x64或macOS 10.12 一.准备工作 (一)开发机器 1. 安装VS2015 .NET Core开发工具:Visual Studio 2015 Tools (Preview 2),下载地址:https://go.microsoft.com/fwlink/?LinkId=827546: 2. 安装.NET Core SDK,下载地址:https://go.microsoft.com/fwlin

将Windows系统编译的.NET Core程序发布到Ubuntu系统

在可移植方面.NET Core应用程序分为两种,Portable application(便捷,需要目标机器安装.NET Core Runtime)和Self-contained application(独立的,又名自宿主.目标机器不需要.NET Core Runtime ), 具体可参考文档:https://docs.microsoft.com/zh-cn/dotnet/articles/core/app-types 本次是将Portable App发布到Ubuntu 16.04上运行.发布方

如何在Centos里面,把.net core程序设为开机自启动

确定你的.net core程序可以在centos手动启动后,下一步,就是把这个程序做成一个服务,让它开机自自动了 1.创建脚本文件 到目录/etc/rc.d/init.d下面,创建一个myserver.sh文件 vi myserver.sh 内容如下: #!/bin/bash# chkconfig: 2345 10 30# description: testServer dotnet /home/yourapp.dll #!/bin/bash符号#!用来告诉系统它后面的参数是用来执行该文件的程序

使用VS2013进行C#程序的单元测试(转)

没有按照预期的那样做出成功的单元测试,磕磕绊绊参照了下面两篇博客大致做出来了,所以很有必要记录一下过程. http://www.cnblogs.com/duasonir/p/5299732.html(照着这个我成功的做出了单元测试) http://www.cnblogs.com/Look_Sun/p/4514732.html(这个我几乎研究了一天,但是最后还是没有做出来,最后看到上面那位同学的参考的内容和这篇一样,拜读之后也作出了半成品) 由于程序都是简单加法,而且我自己的思想并没有加入其中,项

.net core程序部署

前期将一些程序切换到了.net core,本文这里记录下windows 下.net core程序部署相关的方法.有同样需求的朋友可以参考一下,以免少走一些弯路. .net core程序部署主要工作就是在目标机器上装上.net core runtime,它可以在微软官方的下载网站上下载. 官方的图比较清晰的介绍了其运行环境,就windows的.net core程序部署而言,主要需要安装如下两个包: .net core runtime asp.net core runtime 如果部署的是.net