xUnit.net是針對.NET Framework的免費,開源,以社區爲中心的單元測試工具。git
單元測試能夠測試某個類或方法,具備較高的深度,對應用的功能覆蓋面很小。
集成測試有更好的廣度,能夠測試web資源,數據庫資源等。
皮下測試在web中針對controller下的節點測試。
UI測試是對應用的界面功能測試。
實際上經常使用的是單元測試和集成測試。web
通常是針對類的Public方法進行測試,也就是對行爲進行測試,若是是私有方法須要改變修飾符才能測試正則表達式
.Net Framework
.Net Core
.Net Standard
UWP
Xamarin數據庫
官網:
https://xunit.netc#
VS自帶的測試瀏覽器(右鍵測試或者ctrl+r,t) resharper, cmd命令行(.net cli): dotnet test dotnet test --help
namespace Demo { public class Calculator { public int Add(int x,int y) { return x + y; } } }
public class CalculatorTests { [Fact] public void ShouldAddEquals5() //注意命名規範 { //Arrange var sut = new Calculator(); //sut-system under test,通用命名 //Act var result = sut.Add(3, 2); //Assert Assert.Equal(5, result); } }
Arrange: 在這裏作一些先決的設定。例如建立對象實例,數據,輸入等。
Act: 在這裏執行生產代碼並返回結果。例如調用方法或者設置屬性。
Assert:在這裏檢查結果,會產生測試經過或者失敗兩種結果。瀏覽器
Assert基於代碼的返回值、對象的最終狀態、事件是否發生等狀況來評估測試的結果
Assert的結果多是Pass或者Fail
若是全部的asserts都經過了,那麼整個測試就經過了。
若是任何assert 失敗了,那麼結果就失敗了。bash
一個test裏應該有多少個assertside
演示示例:
先建一個.net core類庫項目,再創建一個xunit測試項目(參考最後綜合示例)函數
[Fact] [Trait("Category","New")] public void BeNewWhenCreated() { _output.WriteLine("第一個測試"); // Arrange var patient = new Patient(); // Act var result = patient.IsNew; // Assert Assert.True(result); }
[Fact] public void HaveCorrectFullName() { //var patient = new Patient(); _patient.FirstName = "Nick"; _patient.LastName = "Carter"; var fullName = _patient.FullName; Assert.Equal("Nick Carter", fullName); //相等 Assert.StartsWith("Nick", fullName);//以開頭 Assert.EndsWith("Carter", fullName);//以結尾 Assert.Contains("Carter", fullName);//包含 Assert.Contains("Car", fullName); Assert.NotEqual("CAR", fullName);//不相等 Assert.Matches(@"^[A-Z][a-z]*\s[A-Z][a-z]*", fullName);//正則表達式 }
[Fact] [Trait("Category", "New")] public void HaveDefaultBloodSugarWhenCreated() { var p = new Patient(); var bloodSugar = p.BloodSugar; Assert.Equal(4.9f, bloodSugar,5); //判斷是否相等 Assert.InRange(bloodSugar, 3.9, 6.1);//判斷是否在某一範圍內 }
[Fact] public void HaveNoNameWhenCreated() { var p = new Patient(); Assert.Null(p.FirstName); Assert.NotNull(_patient); }
[Fact] public void HaveHadAColdBefore() { //Arrange var _patient = new Patient(); //Act var diseases = new List<string> { "感冒", "發燒", "水痘", "腹瀉" }; _patient.History.Add("發燒"); _patient.History.Add("感冒"); _patient.History.Add("水痘"); _patient.History.Add("腹瀉"); //Assert //判斷集合是否含有或者不含有某個元素 Assert.Contains("感冒",_patient.History); Assert.DoesNotContain("心臟病", _patient.History); //判斷p.History至少有一個元素,該元素以水開頭 Assert.Contains(_patient.History, x => x.StartsWith("水")); //判斷集合的長度 Assert.All(_patient.History, x => Assert.True(x.Length >= 2)); //判斷集合是否相等,這裏測試經過,說明是比較集合元素的值,而不是比較引用 Assert.Equal(diseases, _patient.History); }
/// <summary> /// 測試Object /// </summary> [Fact] public void BeAPerson() { var p = new Patient(); var p2 = new Patient(); Assert.IsNotType<Person>(p); //測試對象是否相等,注意這裏爲false Assert.IsType<Patient>(p); Assert.IsAssignableFrom<Person>(p);//判斷對象是否繼承自Person,true //判斷是否爲同一個實例 Assert.NotSame(p, p2); //Assert.Same(p, p2); }
/// <summary> /// 判斷是否發生異常 /// </summary> [Fact] public void ThrowException() //注意不能使用ctrl+R,T快捷鍵,由於會中斷測試,拋出異常 { var p = new Patient(); //判斷是否返回指定類型的異常 var ex = Assert.Throws<InvalidOperationException>(()=> { p.NotAllowed(); }); //判斷異常信息是否相等 Assert.Equal("not able to create", ex.Message); }
/// <summary> /// 判斷是否觸發事件 /// </summary> [Fact] public void RaizeSleepEvent() { var p = new Patient(); Assert.Raises<EventArgs>( handler=>p.PatientSlept+=handler, handler=>p.PatientSlept -= handler, () => p.Sleep()); }
/// <summary> /// 測試屬性改變事件是否觸發 /// </summary> [Fact] public void RaisePropertyChangedEvent() { var p = new Patient(); Assert.PropertyChanged(p, nameof(p.HeartBeatRate), () => p.IncreaseHeartBeatRate()); }
使用trait特性,對測試進行分組:[Trait("Name","Value")] 能夠做用於方法級和Class級別
相同的分組使用相同的特性。工具
[Fact] [Trait("Category","New")] public void BeNewWhenCreated() { _output.WriteLine("第一個測試"); // Arrange //var patient = new Patient(); // Act var result = _patient.IsNew; // Assert Assert.True(result); //Assert.False(result); }
測試分組搜索: 能夠在測試資源管理器中按分組排列、搜索、運行測試
在dotnet cli中分組測試:
dotnew test --filter 「Category=New」 //運行單個分類測試 dotnew test --filter 「Category=New|Category=Add」//運行多個分類測試 dotnet test --filter Category --logger:trx //輸出測試日誌
使用特性:[Fact(Skip="不跑這個測試")],能夠忽略測試,忽略測試圖標爲黃色警告
使用ITestOutputHelper能夠自定義在測試時的輸出內容
dotnet test --filter Category --logger:trx會輸出測試日誌trx結尾的文件
public class PatientShould:IClassFixture<LongTimeFixture>,IDisposable { private readonly ITestOutputHelper _output; private readonly Patient _patient; private readonly LongTimeTask _task; public PatientShould(ITestOutputHelper output,LongTimeFixture fixture) { this._output = output; _patient = new Patient(); //_task = new LongTimeTask(); _task = fixture.Task; } [Fact] [Trait("Category","New")] public void BeNewWhenCreated() { _output.WriteLine("第一個測試"); // Arrange //var patient = new Patient(); // Act var result = _patient.IsNew; // Assert Assert.True(result); //Assert.False(result); } }
在執行一個方法時,須要很長事件,而在構造函數中new時,每一個測試跑的時候都會new對象或者執行方法,這是致使測試很慢。解決方法:
using Demo2; using System; namespace Demo2Test { public class LongTimeFixture : IDisposable { public LongTimeTask Task { get; } public LongTimeFixture() { } public void Dispose() { } } }
public class PatientShould:IClassFixture<LongTimeFixture>,IDisposable { private readonly ITestOutputHelper _output; private readonly Patient _patient; private readonly LongTimeTask _task; public PatientShould(ITestOutputHelper output,LongTimeFixture fixture) { this._output = output; _patient = new Patient(); //_task = new LongTimeTask(); _task = fixture.Task;//獲取方法 } }
1.在上一個的繼承上,先創建一個TaskCollection類,實現ICollectionFixture<LongTimeFixture>接口,注意不能有反作用,不然會影響結果
using Xunit; namespace Demo2Test { [CollectionDefinition("Lone Time Task Collection")] public class TaskCollection:ICollectionFixture<LongTimeFixture> { } }
[Collection("Lone Time Task Collection")] public class PatientShould:IClassFixture<LongTimeFixture>,IDisposable { private readonly ITestOutputHelper _output; private readonly Patient _patient; private readonly LongTimeTask _task; public PatientShould(ITestOutputHelper output,LongTimeFixture fixture) { this._output = output; _patient = new Patient(); //_task = new LongTimeTask(); _task = fixture.Task;//獲取方法 } }
[Theory] [InlineData(1,2,3)] [InlineData(2,2,4)] [InlineData(3,3,6)] public void ShouldAddEquals(int operand1,int operand2,int expected) { //Arrange var sut = new Calculator(); //sut-system under test //Act var result = sut.Add(operand1, operand2); //Assert Assert.Equal(expected, result); }
using System.Collections.Generic; namespace DemoTest { public class CalculatorTestData { private static readonly List<object[]> Data = new List<object[]> { new object[]{ 1,2,3}, new object[]{ 1,3,4}, new object[]{ 2,4,6}, new object[]{ 0,1,1}, }; public static IEnumerable<object[]> TestData => Data; } }
/// <summary> /// 數據共享-MemberData /// </summary> /// <param name="operand1"></param> /// <param name="operand2"></param> /// <param name="expected"></param> [Theory] [MemberData(nameof(CalculatorTestData.TestData),MemberType =typeof(CalculatorTestData))] public void ShouldAddEquals2(int operand1, int operand2, int expected) { //Arrange var sut = new Calculator(); //sut-system under test //Act var result = sut.Add(operand1, operand2); //Assert Assert.Equal(expected, result); }
using System.Collections.Generic; using System.IO; using System.Linq; namespace DemoTest.Data { /// <summary> /// 讀取文件並返回數據集合 /// </summary> public class CalculatorCsvData { public static IEnumerable<object[]> TestData { get { //把csv文件中的數據讀出來,轉換 string[] csvLines = File.ReadAllLines("Data\\TestData.csv"); var testCases = new List<object[]>(); foreach (var csvLine in csvLines) { IEnumerable<int> values = csvLine.Trim().Split(',').Select(int.Parse); object[] testCase = values.Cast<object>().ToArray(); testCases.Add(testCase); } return testCases; } } } }
1,2,3 1,3,4 2,4,6 0,1,1
/// <summary> /// 數據共享-MemberData-外部數據 /// </summary> /// <param name="operand1"></param> /// <param name="operand2"></param> /// <param name="expected"></param> [Theory] [MemberData(nameof(CalculatorCsvData.TestData), MemberType = typeof(CalculatorCsvData))] public void ShouldAddEquals3(int operand1, int operand2, int expected) { //Arrange var sut = new Calculator(); //sut-system under test //Act var result = sut.Add(operand1, operand2); //Assert Assert.Equal(expected, result); }
using System.Collections.Generic; using System.Reflection; using Xunit.Sdk; namespace DemoTest.Data { public class CalculatorDataAttribute : DataAttribute { public override IEnumerable<object[]> GetData(MethodInfo testMethod) { yield return new object[] { 0, 100, 100 }; yield return new object[] { 1, 99, 100 }; yield return new object[] { 2, 98, 100 }; yield return new object[] { 3, 97, 100 }; } } }
/// <summary> /// 數據共享-自定義特性繼承自DataAttribute /// </summary> /// <param name="operand1"></param> /// <param name="operand2"></param> /// <param name="expected"></param> [Theory] [CalculatorDataAttribute] public void ShouldAddEquals4(int operand1, int operand2, int expected) { //Arrange var sut = new Calculator(); //sut-system under test //Act var result = sut.Add(operand1, operand2); //Assert Assert.Equal(expected, result); }
源碼:https://gitee.com/Alexander360/LearnXUnit