縱軸表示測試的深度,也就是說測試的細緻程度。git
橫軸則表示測試的覆蓋程度。github
從速度來看 單元是最快的,而UI測試是最慢的。正則表達式
從脆弱性來看 UI測試是最差的,程序修改後極有可能須要修改測試代碼,而單元測試是最好的。數據庫
public void IncreaseHeartBeatRate() { HeartBeatRate = CalculateHeartBeatRate() + 2; } private int CalculateHeartBeatRate() { var random = new Random(); return random.Next(1, 100); }
大多數狀況下單元測試都應該是針對類的行爲進行測試的,也就是public方法。固然也純在不一樣的觀點。框架
若是想要對private方法進行測試的話,是有不少缺點的:dom
[assembly: InternalsVisibleTo("Hospital.Tests")]
這表示Hospital.Tests這個測試項目能夠訪問該項目生產代碼(production code)的internal方法。工具
官網:https://xunit.github.io/單元測試
xUnit是一個測試框架,能夠針對.net/core進行測試。測試
測試項目需引用被項目從而對其進行測試,測試項目同時須要引用xUnit庫。測試編寫好後,用Test Runner來運行測試。Test Runner能夠讀取測試代碼,而且會知道咱們所使用的測試框架,而後執行,並顯示結果。目前可用的Test Runner包括vs自帶的Test Explorer,或者dotnet core命令行,以及第三方工具,例如resharper等等。ui
.net full, .net core, .net standard, uwp, xamarin.
[Fact] public void TestIncreaseHeartBeatRate() { var patient = new Patient(); // Arrange patient.IncreaseHeartBeatRate(); // Act Assert.InRange(patient.HeartBeatRate, 40, 100); // Assert }
首先創建一個C# library項目,叫Hospital(下面部分截圖有個拼寫錯誤,應該是Hospital),而後創建一個xUnit Test項目,叫Hospital.Tests:
能夠看到Hospital.Tests已經包含裏這幾個庫:
而後爲Hospital.Tests添加到Hospital項目的引用。
首先把剛纔創建的Hospital.Tests項目移除(目錄須要手動刪除).
而後打開項目位置:
按住shift打開命令行:
用命令行建立項目:
建立 Hospital.Tests目錄,進入目錄,使用命令dotnet new xunit
建立xUnit單元測試項目。
添加項目的引用:
最後添加項目到解決方案:
回到VS界面,提示從新加載:
確認後,VS中解決方案結構如:
對測試項目的文件名進行一些重構,編寫如下代碼,並進行Build:
從Test Explorer咱們能夠看到一個待測試的項目。
在這裏,咱們能夠對測試項目進行分組和排序,如圖:
想要運行全部的測試,就點擊上面的Run All按鈕。若是像運行單個測試,那麼右擊選擇Run Selected Tests:
運行後,能夠看到結果,Passed:
咱們一樣能夠經過命令行來進行測試:
進入到Tests目錄,執行 dotnet test
命令,全部的測試都會被發現,而後被執行:
由於咱們並無在測試方法中寫任何的Assert,因此測試確定是經過的,但這個測試也是個無效的測試。
Assert作什麼?Assert基於代碼的返回值、對象的最終狀態、事件是否發生等狀況來評估測試的結果。Assert的結果多是Pass或者Fail。若是全部的asserts都pass了,那麼整個測試就pass了;若是有任何assert fail了,那麼測試就fail了。
xUnit提供瞭如下類型的Assert:
一種建議的作法是,每一個test方法裏面只有一個assert。
而還有一種建議就是,每一個test裏面能夠有多個asserts,只要這些asserts都是針對同一個行爲就行。
目標類:
public class Patient { public Patient() { IsNew = true; } public string FirstName { get; set; } public string LastName { get; set; } public string FullName => $"{FirstName} {LastName}"; public int HeartBeatRate { get; set; } public bool IsNew { get; set; } public void IncreaseHeartBeatRate() { HeartBeatRate = CalculateHeartBeatRate() + 2; } private int CalculateHeartBeatRate() { var random = new Random(); return random.Next(1, 100); } }
測試類:
public class PatientShould { [Fact] public void HaveHeartBeatWhenNew() { var patient = new Patient(); Assert.True(patient.IsNew); } }
運行測試:
結果符合預期,測試經過。
改成Assert.False()的話:
測試Fail。
測試string是否相等:
[Fact] public void CalculateFullName() { var p = new Patient { FirstName = "Nick", LastName = "Carter" }; Assert.Equal("Nick Carter", p.FullName); }
而後你須要Build一下,這樣VS Test Explorer才能發現新的test。
運行測試,結果Pass:
一樣改一下Patient類(別忘了Build一下),讓結果失敗:
從失敗信息能夠看到期待值和實際值。
StartsWith, EndsWith
[Fact] public void CalculateFullNameStartsWithFirstName() { var p = new Patient { FirstName = "Nick", LastName = "Carter" }; Assert.StartsWith("Nick", p.FullName); } [Fact] public void CalculateFullNameEndsWithFirstName() { var p = new Patient { FirstName = "Nick", LastName = "Carter" }; Assert.EndsWith("Carter", p.FullName);e); }
Build,而後Run Test,結果Pass:
忽略大小寫 ignoreCase:
string默認的Assert是區分大小寫的,這樣就會失敗:
能夠爲這些方法添加一個參數ignoreCase設置爲true,就會忽略大小寫:
包含子字符串 Contains
[Fact] public void CalculateFullNameSubstring() { var p = new Patient { FirstName = "Nick", LastName = "Carter" }; Assert.Contains("ck Ca", p.FullName); }
Build,測試結果Pass。
正則表達式,Matches
測試一下First name和Last name的首字母是否是大寫的:
[Fact] public void CalculcateFullNameWithTitleCase() { var p = new Patient { FirstName = "Nick", LastName = "Carter" }; Assert.Matches("[A-Z]{1}{a-z}+ [A-Z]{1}[a-z]+", p.FullName); }
Build,測試經過。
首先爲Patient類添加一個property: BloodSugar。
public class Patient { public Patient() { IsNew = true; _bloodSugar = 5.0f; } private float _bloodSugar; public float BloodSugar { get { return _bloodSugar; } set { _bloodSugar = value; } } ...
Equal:
[Fact] public void BloodSugarStartWithDefaultValue() { var p = new Patient(); Assert.Equal(5.0, p.BloodSugar); }
Build,測試經過。
範圍, InRange:
首先爲Patient類添加一個方法,病人吃飯以後血糖升高:
public void HaveDinner() { var random = new Random(); _bloodSugar += (float)random.Next(1, 1000) / 100; // 應該是1000 }
添加test:
[Fact] public void BloodSugarIncreaseAfterDinner() { var p = new Patient(); p.HaveDinner(); // Assert.InRange<float>(p.BloodSugar, 5, 6); Assert.InRange(p.BloodSugar, 5, 6); }
Build,Run Test,結果Fail:
能夠看到期待的Range和實際的值,這樣很好。若是你使用Assert.True(xx >= 5 && xx <= 6)
的話,錯誤信息只能顯示True或者False。
由於HaveDinner方法裏,表達式的分母應該是1000,修改後,Build,Run,測試Pass。