第1部分: http://www.cnblogs.com/cgzl/p/8283610.htmlhtml
第2部分: http://www.cnblogs.com/cgzl/p/8287588.htmlide
第3部分: http://www.cnblogs.com/cgzl/p/8438019.html測試
請使用這個項目的代碼: https://pan.baidu.com/s/1i7d8z2Hui
打開PlayerCharacterShould.csspa
添加幾個Fact測試方法:code
[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了.htm
仔細看下這4個方法, 他們實際上是作了一樣的事情, 只不過輸入的數據和期待的結果不一樣而已. blog
因此咱們應該重構一下這段代碼.文檔
針對上述狀況, 咱們就再也不使用Fact屬性標籤了, 而是須要使用Theory.get
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簡介就到此爲止了, 想要深刻了解的話, 仍是看官方文檔吧.