使用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簡介就到此為止了, 想要深入了解的話, 還是看官方文檔吧. 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM