使用xUnit爲.net core程序進行單元測試(1)

導讀

爲何要編寫自動化測試程序(Automated Tests)?

  • 能夠頻繁的進行測試
  • 能夠在任什麼時候間進行測試,也能夠按計劃定時進行,例如:能夠在半夜進行自動測試。
  • 確定比人工測試要快。
  • 能夠更快速的發現錯誤。
  • 基本上是很是可靠的。
  • 測試代碼與生產代碼緊密結合。
  • 使得開發團隊更具備幸福感!

自動化測試的分類:

縱軸表示測試的深度,也就是說測試的細緻程度。git

橫軸則表示測試的覆蓋程度。github

  • Unit Test 單元測試, 它能夠測試一個類,或者一個類的某個功能,它具備很好的深度,可是對整個應用來講它不具有很好的覆蓋面。
  • Integration Test 集成測試,它沒有單元測試那麼細緻,可是具備相對較好的測試覆蓋面。例如它能夠測試功能的組合,以及像數據庫或文件系統這樣的外部資源等。
  • Subcutaneous Test 皮下測試,這種測試做用於UI層的下面一層,這也意味着它對整個應用來講有很好的覆蓋率,可是深度欠佳。那一個MVC結構的應用來講,它就是針對恰好在Controller下面一層的測試,對於Web service來講,它就是對節點下面那層的測試。
  • UI測試,它的測試覆蓋面很廣,直接從UI層面進行測試,可是深度欠佳。

從速度來看 單元是最快的,而UI測試是最慢的。正則表達式

從脆弱性來看 UI測試是最差的,程序修改後極有可能須要修改測試代碼,而單元測試是最好的。數據庫

是測試行爲仍是測試私有方法(private method)?

        public void IncreaseHeartBeatRate()
        {
            HeartBeatRate = CalculateHeartBeatRate() + 2;
        }

        private int CalculateHeartBeatRate()
        {
            var random = new Random();
            return random.Next(1, 100);
        }

大多數狀況下單元測試都應該是針對類的行爲進行測試的,也就是public方法。固然也純在不一樣的觀點。框架

若是想要對private方法進行測試的話,是有不少缺點的:dom

  • 首先須要修改方法的訪問限制須要從private改成public,這就破壞了面向對象的封裝性。
  • 再者,這其實測試的是類的具體實現細節,而不是類的行爲。若是咱們想要對類的內部進行重構的話,就會破壞測試,致使測試也必須重構。若是必須對private方法進行測試,那麼首先建議您把private修飾符改爲internal,而後修改該項目(project)的AssemblyInfo.cs,它在項目的Debug或者Release文件夾下。代碼以下:
[assembly: InternalsVisibleTo("Hospital.Tests")]

這表示Hospital.Tests這個測試項目能夠訪問該項目生產代碼(production code)的internal方法。工具

測試的三個階段 AAA

  • Arrange,這裏作一些先決的設定。例如建立對象實例,數據,輸入等等。
  • Act,在這裏執行生產代碼並返回結果。例如調用方法,或者設置屬性(Properties)。
  • Assert,在這裏檢查結果。測試經過或者失敗。

xUnit.net

官網:https://xunit.github.io/單元測試

xUnit是一個測試框架,能夠針對.net/core進行測試。測試

測試項目需引用被項目從而對其進行測試,測試項目同時須要引用xUnit庫。測試編寫好後,用Test Runner來運行測試。Test Runner能夠讀取測試代碼,而且會知道咱們所使用的測試框架,而後執行,並顯示結果。目前可用的Test Runner包括vs自帶的Test Explorer,或者dotnet core命令行,以及第三方工具,例如resharper等等。ui

xUnit支持的平臺:

.net full, .net core, .net standard, uwp, xamarin.

xUnit的例子:

        [Fact]
        public void TestIncreaseHeartBeatRate()
        {
            var patient = new Patient(); // Arrange
            patient.IncreaseHeartBeatRate(); // Act
            Assert.InRange(patient.HeartBeatRate, 40, 100); // Assert
        }

安裝配置xUnit.net

a.使用Visual Studio 2017

首先創建一個C# library項目,叫Hospital(下面部分截圖有個拼寫錯誤,應該是Hospital),而後創建一個xUnit Test項目,叫Hospital.Tests:

 

能夠看到Hospital.Tests已經包含裏這幾個庫:

而後爲Hospital.Tests添加到Hospital項目的引用。

b.使用.net core 命令行

首先把剛纔創建的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基於代碼的返回值、對象的最終狀態、事件是否發生等狀況來評估測試的結果。Assert的結果多是Pass或者Fail。若是全部的asserts都pass了,那麼整個測試就pass了;若是有任何assert fail了,那麼測試就fail了。

xUnit提供瞭如下類型的Assert:

  • boolean:True/False
  • String:相等/不等,是否爲空,以..開始/結束,是否包含子字符串,匹配正則表達式
  • 數值型:相等/不等,是否在某個範圍內,浮點的精度
  • Collection:內容是否相等,是否包含某個元素,是否包含知足某種條件(predicate)的元素,是否全部的元素都知足某個assert
  • Raised events:Custom events,Framework events(例如:PropertyChanged)
  • Object Type:是不是某種類型,是否某種類型或繼承與某種類型

一個test裏應該有多少個asserts?

一種建議的作法是,每一個test方法裏面只有一個assert。

而還有一種建議就是,每一個test裏面能夠有多個asserts,只要這些asserts都是針對同一個行爲就行。

第一個Assert

目標類:

    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 Assert

測試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,測試經過。

數值 Assert

首先爲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。

相關文章
相關標籤/搜索