zoukankan      html  css  js  c++  java
  • 使用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简介就到此为止了, 想要深入了解的话, 还是看官方文档吧. 

  • 相关阅读:
    methodForSelector
    判定一个点P是否存在于指定的三角形ABC内
    xcode error failed to launch no such file or directory
    【转】SQLServer系统变量使用
    【转】SQL SERVER中查询某个表或某个索引是否存在
    wtforms
    Flask上下文管理、session原理和全局g对象
    Oldboy s4 Flask
    批处理删除文件夹下所有文件和文件夹
    autojs使用
  • 原文地址:https://www.cnblogs.com/cgzl/p/8444423.html
Copyright © 2011-2022 走看看