.netcore持續整合測試篇之搭建記憶體伺服器進行整合測試一

周國通發表於2019-08-13

系列目錄

在web專案裡,我們把每一層的程式碼的單元測試都通過並不代表程式能正常執行,因為這個過程缺失了http管道,很多時候我們還還需要把專案布在iis環境中或者在vs裡啟動iis express伺服器進行整合測試.對於返回view的的方法我們通常是在瀏覽器中輸入地址進行測試,對於返回資料的方法則使用諸如postman,fiddler,httpMaster pro,http debugger等工具進行測試.在這些工具中,postman相對好用,不但提供了測試資料的分組功能,還可以編寫指令碼來完成一些自動化工作,比如自動新增固定http頭等.然而即便諸如postman這麼強大,依賴有很多不方便的地方.一旦方法過多,管理起來很不方便.並且構造複雜json或者formdata資料更是讓人心力絞悴,稍有一點格式錯誤就導致整個測試通不過,嚴重影響對錯誤點位置的判斷.更為重要的一點是,我們不論是在頁面中輸入地址,還是使用postman工具更多的是被動的去解決問題,也就是說等錯誤出現了以後我們才去測試相應的方法,很少見有誰使用postman把所有方法全部都跑一遍.

幸運的是.微軟在把.net升級到.net core之後,asp.net core 可以搭建一個記憶體伺服器來完成整合測試,這樣我們便可以不依賴於特定環境來測試我們的程式碼.這樣我們就在專案釋出前可以先執行一下整合測試確保各方法都是可訪問的,減少專案上線後出現一些簡單錯誤的機率.

下面我們介紹如何搭建整合測試環境

產生我們使用VisualStudio自帶的模板建立一個Asp.net core web專案,在出現的對話方塊選項中選擇mvc(當然也可以不使用mvc專案,這裡不了演示方便起見).新建完mvc專案後,預設會有一個HomeController,想必大家都很熟悉了.此時我們再新建一個HelloWorld控制器,這個控制器目前只有一個Hello方法,程式碼如下

 public class HelloWorldController : Controller
    {
        public IActionResult Hello()
        {
            return Content("Hello,World");
        }
    }

然後我們使用VisualStudio自帶的Xunit模板建立一個測試專案(新建專案的時候切換到.net core標籤,裡面有Xunit測試專案模板)

此時,在測試專案裡需要新增Microsoft.AspNetCore.TestHost Nuget包才可以進行整合測試.

需要注意的是,這個包要安裝在測試專案裡,而不是core Mvc專案裡

我們新建一個名為mvc20的測試類,程式碼如下

  public class mvc20
    {
        private readonly HttpClient _client;

        public mvc20()
        {
            var builder = new WebHostBuilder()
                .UseContentRoot(@"E:\personal project\newTest2018\ConsoleApp1\CoreMvc")
                .UseEnvironment("Development")
                .UseStartup<CoreMvc.Startup>();
            var server = new TestServer(builder);
             _client = server.CreateClient();
        }

        [Fact]
        public async Task SimpleGet()
        {
            var response = await _client.GetAsync("/HelloWorld/Hello");
            response.EnsureSuccessStatusCode();
            var responseStr = await response.Content.ReadAsStringAsync();
            Assert.Equal("Hello,World", responseStr);
        }
    }

配置詳解

下面我們來詳細分析這段程式碼

我們先來看建構函式裡的程式碼,這裡的第一部分是建立一個WebHostBuilder,如果瞭解過.net core的同事可能對這段程式碼感到很熟悉,其實mvc專案也是通過WebHostBuilder建立一個webhost,我們開啟剛建立的mvc專案,開啟program入口類可以看到如下程式碼

 public static void Main(string[] args)
        {
            BuildWebHost(args).Run();
        }

        public static IWebHost BuildWebHost(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>()
                .Build();

可以看到BuildWebHost方法裡面使用了WebHost類來建立一個預設WebHostBuilder,這個方法裡面也有UseStartup方法,其實像親朋ContentRoot,UseEnvironment這些方法也可以在mvc專案裡用,早期的.net core 專案模板生成的方法確實是這樣的,只是在.net core 2.0進行了更進一步的抽象,ContentRoot路徑,預設工作環境都按照慣例提供,不再需要手動指定.但是測試專案的工作環境和mvc專案的執行環境並不在同一個目錄,因此這裡需要手工指定.

UseContentRoot

此方法用於指定專案執行時資原始檔的根目錄,通常情況下是建立專案的目錄,以上目錄大家根據建立專案的路徑來選擇,不要盲目拷貝以上程式碼.

UseEnvironment

此方法用於指定專案的執行環境,這裡我們指定為Development環境,這個環境是開發環境約定的預設環境,至於為什麼需要指定工作環境,因為很多時候開發環境和線上環境中使用到的元件是不一樣的,這在.net core專案裡尤其明顯,比如說線上環境中我們使用的是正式的EntityFramework,而在工作環境下為了效率我們更傾向使用一個記憶體EntityFramework.還有一些工具類的框架只是在開發環境中使用的,線上環境並不需要它.

這個概念可能很容易理解,但是相信大家仍然看的一頭蒙圈,為什麼是Development,而不是Develop,debug呢,其實這個變數名是在mvc專案下的Properties裡面的launchSettings.json裡定義的.

avatar

因此如果我們提供的引數是"Development",mvc專案啟動的時候就把它和這個檔案匹配,如果匹配成功則當前工作環境就是Development環境.

到於它是怎麼用,可以檢視Startup.cs檔案,裡面會注入IHostingEnvironment 物件例項,其中裡面的env.IsDevelopment就是通過這個值來判斷當前工作環境是否是Development環境,如果是工作環境,我們可以新增一些只有在工作環境中使用的程式碼.

UseStartup

此方法用於指定一個Startup檔案,Startup檔案在mvc5專案裡也有,主要是關於中介軟體的配置,只是mvc5在啟動的時候自動呼叫一個名為Startup的檔案(mvc5專案裡這個檔案不是必須的),而.net core裡顯式指定一個startup檔案,這樣就必須指定一個startup檔案(名稱不必須是startup,可以是任意名,這裡是顯式指定的,因此程式中能找到). net core專案裡startup檔案必須指定否則整個專案就是一個普通操控臺應該程式,沒法實現http功能,不論是mvc,webapi還是基本的http請求在.net core裡都作為一箇中介軟體,要使用必須配置.

回到測試方法裡,這個startup檔案直接指定為mvc專案裡的startup檔案即可,並且必須要這樣,如果測試環境中的startup檔案和mvc專案裡的不一樣測試就顯得沒有意義了.

TestServer

在測試環境裡通過new方式建立一個TestServer,建構函式接收一個IWebHostBuilder型別的引數,我們把上面建立的WebHostBuilder傳入即可.

建立HttpClient

TestServer物件可以建立一個HttpClient物件,利用HttpClient物件我們便可以構建Http請求了.

需要說明的是這個HttpClient並沒什麼特殊的,它就是System.net.http下的httpclient,想必大家多少都用到過.

下面我們來看測試方法,測試方法主要就是通過HttpClient來傳送Http請求,這裡的程式碼相信大家都並不陌生.

需要特別注意的是,這個測試方法和以往的不太相同,是一個async Task標識的非同步方法,之所以要使用非同步方法是因為HttpClient裡的方法都是非同步的,如果嘗試使用同步的方法獲取結果,很容易造成死鎖.可能大家在工作中也確實使用過xxx.Result來同步阻塞獲取非同步結果,並且能正確返回真,但是阻塞HttpClient裡的非同步方法很多時候都會造成死鎖.至於原因大家可以檢視這篇文章

https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html

小貼士,如何在Main方法裡面呼叫非同步函式,C# 7.1之前版本的Main方法是不支援非同步Main方法的,這給測試非同步方法帶來了很大的不便,c# 7.1開始支援非同步Main方法,我們可以把Main方法標識為Async Task Main(string[] args).然後我們在visual studio裡對著專案右鍵點選屬性,在Build標籤下選擇高階,彈出的對話方塊里語言版本下拉選單選擇c# 7.1或者更高(寫本文時,VisualStudio最高支援C# 7.3).這樣非同步Main方法便能編譯通過了.

avator

下面言歸正傳,測試方法裡的程式碼相必大家都很熟悉了,這裡有一點可能有些同事沒有遇到過那就是EnsureSuccessStatusCode從字面上看它的意思是保證成功狀態,實際上它並不是保證請求一定能夠返回成功,而是返回的狀態碼不是200的時候就丟擲一個異常.

需要注意的是EnsureSuccessStatusCode請求返回的必須是200才能通過,201,204這樣的狀態碼也會丟擲異常.大家在開發過程中一定要根據實際情況來決定是否使用它.一般情況下前後端分離的專案裡很多專案組都對http請求返回的結果進行了封閉,不管成功或者失敗都返回200,具體成功失敗是通過一個額外的其它欄位來確實的.這種情況下需要使用EnsureSuccessStatusCode,如果有對外介面很多時候會返回201,204,304,403等http狀態,這時候如果 使EnsureSuccessStatusCode則結果不是我們期待的.

相關文章