1. 程式人生 > >.netcore持續整合測試篇之開篇簡介及Xunit基本使用

.netcore持續整合測試篇之開篇簡介及Xunit基本使用

系列目錄

為了支援跨平臺,微軟為.net平臺提供了.net core test sdk,這樣第三方測試框架諸如Nunit,Xunit等只需要按照sdk提供的api規範進行開發便可以被dotnet cli工具呼叫,這樣就解決了在持續整合過程中第三方框架依賴於windows平臺上的各自runner的問題,使得測試框架開發者不需要花費很大功夫就可以快速遷移到.net core平臺,同時封裝了各測試框架的實現細節,對外暴露統一呼叫介面,大大減少devops開發者的工作量.

作為單元測試基礎知識介紹,這裡只介紹常用單元測試框架Nunit和Xunit如何在.net core平臺上使用,並介紹由於.net core的變化所引起的需要注意的測試程式碼的相應改變,對於如何在Jenkins環境中自動完成測試的相關內容Jenkins基礎知識裡面介紹

考慮到實際工作中可能有的專案組已經使用或者嘗試使用xunit,並且.net core對xunit支援較好,諸多開源專案的單元測試也都使用的是Xunit,本章節作為補充對Xunit基礎知識進行講解,以幫助還不太瞭解這個框架的同學快速入門.

我們知道在.net framework下相要對mvc專案進行整合測試非常困難,一方面.net http管道里有很多黑盒子,開發者對它的實現細節一無所知,二者需要mock的物件太多,工作量巨大.因此很難在持續整合環境中對web專案進行整合測試.開發者或者測試人員大都依賴postman,fiddler,以及瀏覽器的http請求外掛來進行整合測試,這樣帶來的一個很大問題這些http請求很難複用,更難以有效的組織管理.即使使用postman這樣強大的http工具如果測試介面過多程式碼也會變得一塌糊塗,過一段時間後想要知道哪個方法是測試哪個介面用的就需要通過搜尋來導航到指定的測試方法,並且很多時候有於各模組有相同名稱的方法,往往需要先找到方法所在的area,然後再找到controller然後再找到相應方法...如果出現問題的程式碼過多往往把開發者搞的焦頭爛額,苦不堪言.

幸運的是在.net core裡很容易模擬一個httpt管道,這一方面使得整合測試在持續整合環境中使用提供了可能,另一方http請求寫成程式裡,可以很方便的導航到指定的測試方法,極大提供可維護性.本章節最後會介紹如何搭建一個.net core web專案的selfhost環境以供在單元測試框架中使用.

下面我們將簡要介紹如何在vs中配置xunt環境以及Xunit斷言的基本使用

.net core 中使用Xunit

Xunit是.net平臺下的一款單元測試工具,類似Nunit.但是更為輕量,更加專注於單元測試而不像Nunit提供了很多額外的功能

.net core對Xunit支援較好,VisualStudio 2017提供有一個Xunit單元測試模板可以很方便的建立一個Xunit單元測試專案.


如圖,在visual studio裡建立專案時,選擇.net core專案,然後從模板裡面找到Xunit單元測試專案便可以建立一個Xunit單元測試專案了.

我們開啟剛建立的專案右鍵選擇"Nuget包管理",從包管理工具裡我們可以看到,實際上這個模板引用了xunit,和xunit.runner.visualstudio這兩個包.這樣,我們也可以自己手動建立一個.net core型別的庫檔案,然後引入這兩個包,能達到同樣的效果.

這裡建議大家通過模板來建立單元測試專案,因為單元測試框架不同版本可能需要引用不同的包,沒有經驗的同學常常由於引用的包不對導致單元測試專案跑不起來,搞得灰頭土臉,非常鬱悶.

我們編寫以下單元測試程式碼

 public class UnitTest1
    {
        [Fact]
        public void Test1()
        {
            int intt = 3 + 2;
            Assert.True(intt==5);
        }
    }

通過以上程式碼我們看到Xunit就測試斷言和Nunit很類似(這裡指Nunit3,早期版本Nunit差異較大,建議大這在工作中也儘量選用Nunit3,而不是1或者2)

這裡有一點差異需要指出,Xunit並不需要對單元測試類進行註解(Nunit是需要的,否則無法識別),只需要在需要測試的方法上加上fact註解即可.

單元測試方法的執行也和前面講的Nunit單元測試執行方法相同,這裡不再贅述.

常見基本斷言

雖然Xunit和Nunit在斷言上有很多相似的地方,並且有越來越像的趨勢,但是仍然有不少差別,因此這裡仍然會對Xunit的斷言功能進行一個全面的列舉,以供大家速查.並且有時候會指出它和Nunit的差別或者指出Nunit中比較難以實現或者技巧性很強的功能如何在Xunit裡實現.如果有讀者直接閱讀本章節而沒有了解過Nunit,可以有選擇的略過二者比較的內容.

這裡首先指出一個很大的差別.Xunit裡並沒有像Nunit裡的stringAssertion,FileAssertion和CollectionAssertion,而是所有的斷言都在Assert靜態類裡,方法也不是很多,語義也相對更加明確,很適應沒有單元測試基礎的同學快速入門.

下面開始介紹Xunit裡的斷言方法

Assert.Null

用於斷言一個物件是否是Null

這個方法有一個相對含義的斷言就是Assert.NotNull,很多其它的方法也有帶Not的斷言,很容易理解.

Assert. Assert.Equal

此方法有很多過載,用於比較兩個字串,int,decimal或者物件型別是否相等.

注意這裡比較兩個物件是否相等時,相當於Object.equals()來比較兩個物件是否相等

這個方法用於比較兩個double型別值是否相等時,可以指定精度.

Assert.StrictEqual

從字面上來看,它用於比較兩個物件是否是嚴格相等,然而它的表現行為和以上Equal方法非常類似,並不是比較兩個物件記憶體地址是否相等.實際上它是在比較的時候指定一個預設的比較器.這個方法著實非常讓人困惑.

Assert.same

用於比較兩個物件執行是是否指定同一塊記憶體地址,如果要比較兩個物件是否完全相等,則使用它.

注意,雖然Assert.same接收的是兩個object型別物件,但是不建議用它來比較簡單型別,它其實是用於比較兩個物件是否指向同一記憶體地址,因此只有比較引用物件才是有意義的.這個方法應該設計成泛型方法才比較好,不知道為什麼要這樣設計.

Assert.StartsWith Assert.EndsWith

用於斷言一個字串是否以特定字元(串)開頭(結尾),並且這兩個方法都有一個過載用於指定是否忽略大小寫

用過Nunit的同學可能知道,Nunit裡要實現區分大小寫的StartsWith有點麻煩.

Assert.True Assert.False

用於斷言兩個布林變數(包括可空)的值是否是真(假).

Assert.All

用於斷言集合裡的所有元素是否都滿足通過測試,奇怪的是這個方法接收的是Action委託而不是Func<T,bool>型別委託.這將導致寫出來的程式碼看上去有點怪異.

下面舉個用於斷言集合裡的元素值是否都大於0的例子來看看如何使用它

       [Fact]
        public void Test1()
        {
            int[] intt =  {3, 4, 5, 9, 22};
            Assert.All(intt, t => Assert.True(t > 0));
        }

注意以上寫法,由於不接收Func<T,bool>型別委託,因此以上方法不能想當然的寫為 Assert.All(intt, t => t>0); 這樣將導致編譯錯誤.

Assert.Contains

這個方法有多個過載,功能也非常多,但是語義都非常明確.

用於斷言字串是否包含指定字串

相當於字串裡的Contains,並且表現行為類似,也有一個過載支援不區分大小寫

用於斷言集合是否包含指定元素

請看以下程式碼

       [Fact]
        public void Test1()
        {
            int[] intt = {3, 4, 5, 9};
            Assert.Contains(4, intt);
        }

以上程式碼用於斷言集合intt裡是否包含元素4,顯然是包含的

注意,對於包含引用物件的集合判斷是否包含某一元素這一個元素必須和集合中的某一個元素的引用地址一樣.這很多時候並不是我們想要的行為,多數時候引用物件的對應的欄位分別相等時我們就認為它相等,更極端的情況是某些情況下兩個物件只有某一個或者少數幾個欄位相等時我們也認為相等,這要看具體實際業務.前面講Nunit時對這個問題有過詳細講解.這裡Contains方法同樣有一個過載以支援一個比較器,用於自定義相等性邏輯.

還有一點需要指出,Contains方法只能斷言集合是否包含某一個元素,而不能斷言是否包含某幾個元素(也即一個集合是否是另一個集合的子集),Nunit裡並沒有提供直接方法用於處理這樣的問題.有些同學可能認為Assert.Subset是用來解決這個問題的,然而並不是,Subset只能用於實現了ISet介面的集合,很多時候並不是特別有幫助.

用於斷言集合中是否包含指定型別的元素

這個過載方法語義稍顯不是很明確,它其實相當於linq裡的any方法,只要有一個(一些)滿足條件的元素就會返回true

       [Fact]
        public void Test1()
        {
            int[] intt = {3, 4, 5, 9};
            Assert.Contains(intt, a => a > 3);
        }

以上方法用於斷言intt集合中是否包含大於3的元素,顯然是包含的.

Assert.Empty

用於斷言集合是否不包含任何元素,也即集合是否是一個空集合

Assert.Matches

此方法接收兩個引數,第一個表示要匹配的正則規則,第二個表示要測試的字串,語義類似正則表示式裡的IsMatch

Assert.InRange

用於斷言指定元素是否在指定的範圍內

        [Fact]
        public void Test1()
        {
            Assert.InRange(20, 3, 20);
        }

以上程式碼片段斷言20是否在3到20這個範圍內,顯然是在的.

此方法支援一個過載接收一個Icomparer引數用於自定義一個比較器,這樣就可以判斷任意物件是否在某一範圍內,有這方面需求的同學可以研究一下

Assert.Single

用於斷言集合只包含 一個 元素,這個方法有幾個過載.

過載1

用於斷言集合中是僅包含一個元素,這個過載可能大部分時候不是很有用

       [Fact]
        public void Test1()
        {
            Assert.Single(new[] {3});
        }

過載2 接收一個引數,用於斷言這個元素是否是指定元素

      [Fact]
       public void Test1()
       {
           Assert.Single(new[] {3,4,5,9},3);
       }

以上程式碼斷言集體中只有一個元素是3

此方法相當於對集合執行linq的first方法

過載3 接收一個predict型別委託,用於斷言這個元素是否滿足指定條件

      [Fact]
       public void Test1()
       {
           Assert.Single(new[] {3,4,5,9},a=>a>5);
       }

以上方法斷言集合中的這個是否只包含一個大於5的元素.

此方法相當於linq裡面的single方法的有參過載

Assert.IsAssignableFrom

用於斷言例項物件的型別是否是一個型別的子類(或者本身)

這個方法Nunit裡也有,和反射裡的IsAssignableFrom語義相同

Assert.IsType

用於斷言例項物件的型別是否是某一指定型別

和IsAssignableFrom相比,此方法要求例項物件型別必須確切地是某一型別