天天看點

ASP.NET Core Web API 內建測試

ASP.NET Core Web API 內建測試

本文需要您了解ASP.NET Core Web API 和 xUnit的相關知識.

這裡有xUnit的介紹: https://www.cnblogs.com/cgzl/p/9178672.html#test

ASP.NET Core內建測試官方文檔: https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-2.1

內建測試 vs 單元測試

ASP.NET Core Web API 內建測試

測試金字塔, 但它隻是一個指導性的概念.

如果所單元測試是對一個元件進行隔離測試的話, 那麼內建測試則是測試多個元件共同協作産生出期待的結果.

單元測試通常很快. 而內建測試則慢的多, 因為它需要很多配置, 并且可能依賴于外部的元件, 例如資料庫, 網絡, 檔案等.

通常在一個項目裡單元測試要比內建測試多很多.

單元測試通常依賴于mock的元件, 而內建測試則使用可運作的元件.

注意: 如果一個行為可以通過單元測試或內建測試來測試的話, 那麼應該使用單元測試.

如何進行內建測試

如果我想測試一個API Controller的Action, 我可能需要把這個項目運作起來, 等它跑起來, 發送請求并檢驗結果. 但這樣做的話需要很多的配置工作, 并且很麻煩.

幸好ASP.NET Core 提供了一個Microsoft.AspNetCore.TestHost 庫, 使用它就無需單獨去運作被測試系統了.

ASP.NET Core應用裡, 我們在Program.cs裡建立WebHostBuilder, 并配置Kestrel Web伺服器, 使用Startup類進行應用配置, 注冊服務和中間件等. 最終在WebHostBuilder上使用Build()來建立WebHost的執行個體, 它可以用來在特定的URL和端口上運作并監聽請求.

而這個TestHost庫也使用了WebHostBuilder, 但它會自己把建構和運作web宿主的工作處理好, 也就是建立出了一個TestServer. TestServer不會在網絡上進行監聽, TestServer建立了一個名為Host的屬性, 它的類型是IWebHost, 它可以用來處理記憶體裡的請求對象. TestServer還會暴露一個HttpClient, 你可以用它來發送請求到被測試系統. 整個互動的過程都是在記憶體裡完成的.

下圖是被測試系統在生産環境和內建測試使用TestServer情形下的對比圖:

ASP.NET Core Web API 內建測試

圖中:

當應用/被測試系統在生産環境運作的時候, 它使用Kestrel伺服器, 監聽HTTP請求, 并把它轉化為HttpContext, 然後再傳進ASP.NET Core的管道裡.

TestServer不監聽網絡請求, 它使用HttpClient在記憶體裡發送請求.

仔細看一下內建測試時使用TestServer的流圖:

ASP.NET Core Web API 內建測試

圖中可以看到: 測試代碼建立TestServer, TestServer建立HttpClient. 測試代碼使用HttpClient發送請求接收響應. TestServer會轉化請求并交給ASP.NET Core MVC/API 應用來處理.

一個例子

首先需要為你的應用建立內建測試項目:

ASP.NET Core Web API 內建測試

然後需要為項目添加Microsoft.AspNetCore.TestHost 這個庫:

ASP.NET Core Web API 內建測試

被測試的是這個Controller的GetRoot()所對應的行為, 而不隻是這個方法:

ASP.NET Core Web API 內建測試

測試傳回NoContent:

ASP.NET Core Web API 內建測試

這裡面按照之前講的順序, 建立IWebHostBuilder, 并用它建立TestServer, 然後TestServer建立HttpClient. 随後就使用httpClient發送請求, 傳回結果, Assert即可.

需要注意的是, 在建立IWebHostBuilder的時候, 我使用了被測試系統的Startup類來進行配置, 并設定的環境是Development.

由于我這個項目可以看作是真實項目, 是以第一次運作該測試的時候, 測試是Fail的. 因為Startup裡面有很多配置并不滿足測試要求.

在我把IpRateLimiting, HttpsRedirection, Authentication, AuthorizeFilter等中間件/元件去掉之後, 測試才通過:

ASP.NET Core Web API 內建測試

是以這就引出了一個問題, Startup裡面的配置在開發時 和 測試時 以及 生産運作時 可能是不太一樣的.

我的Startup裡面已經有很多代碼了, 如果再進行環境判斷, 那就會更亂了.

是以我決定為內建測試建立立一個Startup配置類:

ASP.NET Core Web API 內建測試

ASP.NET Core項目也支援多環境的多個Startup配置類, 這部分内容請參考官方文檔: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/environments?view=aspnetcore-2.1#environment-based-startup-class-and-methods.

然後修改代碼, 使用這個測試專用的Startup即可:

ASP.NET Core Web API 內建測試

測試會通過.

被測試系統有依賴項

下面繼續測試GetRoot方法的另一個路徑, 這個路徑會用到RootController的依賴項IUrlHelper. 

在內建測試裡, 通常情況下是不使用Mocking技術的. 是以在這裡我也不會mock IUrlHelper:

ASP.NET Core Web API 內建測試

這裡沒有mock任何東西. 此外這個被測試的行為需要設定AcceptHeader.

測試會Pass的, TestServer幫我搞定了一切:

ASP.NET Core Web API 內建測試

優化測試配置

寫了兩個測試方法, 又引出了一個新的問題: 這兩個方法有一些共同的設定代碼, 這些設定可能會比較耗資源. 我們可以把這些設定放在構造函數裡面, 但是如果使用Theory并含有多個InlineData的話, 就會多次運作構造函數裡的設定代碼, 可能會非常好資源(時間).

是以我們應該考慮使用test fixture 這裡有介紹: http://www.cnblogs.com/cgzl/p/8438019.html#share

而且我們可以使用WebApplicationFactory來建構TestServer, 使用WebApplicationFactory的好處是可以靈活的進行自定義配置. 

要使用WebApplicationFactory, 需要添加庫: Microsoft.AspNetCore.Mvc.Testing

ASP.NET Core Web API 內建測試

使用該庫之後, 代碼應該如下:

ASP.NET Core Web API 內建測試

但是卻有一個問題, 這裡我選擇的時StartupIntegrationTest. 而電腦環境變量設定的是Development, 而調試測試之後發現走的是StartupDevelopment.

也許這是個Bug? 或者就是這樣的意圖. 那我暫時還是使用原始的方法建立TestServer吧, 下面是我使用的代碼:

建立一個TestServerFixture, 需要使用IDisposable來做清理工作:

ASP.NET Core Web API 內建測試

而測試類注入該Fixture即可:

ASP.NET Core Web API 內建測試

然後重跑測試, 會pass的:

ASP.NET Core Web API 內建測試

一個複雜點的例子

ASP.NET Core Web API 內建測試
ASP.NET Core Web API 內建測試

我要測試這個Controller下CreateProduct方法對應的行為. 該Controller需要很多依賴項, 其中兩個還需要使用資料庫.

通常情況下內建測試裡使用的資料庫和生産環境中使用的資料庫不同, 在測試環境我更傾向于使用記憶體類資料庫.

EF Core裡面至少有兩個記憶體類的資料庫提供商:

  • Microsoft.EntityFrameworkCore.InMemory, 這個都應該知道.
  • Microsoft.EntityFrameworkCore.Sqlite. 雖然說Sqlite通常是把資料儲存到檔案, 但是提供商為它提供了一個記憶體模式, 把資料庫儲存到了記憶體裡.

在StartupIntegrationTest裡, 我就使用InMemory吧;

ASP.NET Core Web API 內建測試

 下面是測試方法的代碼:

ASP.NET Core Web API 內建測試

這代碼其實很簡單, 就是對應着被測試的Controller方法做一些需要的設定即可, 例如Headers, Content-Type等等.

需要注意的是Content-Type是在Content的Header裡設定, 而不是Request的Headers裡設定, 否則會報亂用Header的錯.

該測試會pass:

ASP.NET Core Web API 內建測試

最後針對該行為再做一個Model驗證失敗的測試:

ASP.NET Core Web API 內建測試

沒什麼不同, 就是model的Name屬性超長了.

這個測試同樣會通過:

ASP.NET Core Web API 內建測試

內建測試就簡單介紹這些.......

ASP.NET Core Web API 內建測試