開發一個系統最無聊的過程大概就是解決 BUG 了,尤其是那種嘗試對 null 物件取值的錯誤(Object reference not set to an instance of an object.),這應該是大部分人剛踏入程式設計領域最常碰到的問題。為了從枯燥的解決 BUG 過程中解脫,這篇就來介紹「單元測試」。
「Blazor 的單元測試」跟一般 C# 程式設計不太一樣,主要是「檢查 Component 的頁面呈現邏輯」、「預期產生的 HTML 標籤跟實際頁面有無差異」,畢竟 Blazor 是以後端語言編寫而渲染的前端框架,如果要驗證資料有無錯誤,就是一般 C# 的單元測試了。
目前微軟的測試框架有 MSTest、NUnit 跟 xUnit 三種,「但都不包含 Blazor」,還好有社群愛好者建立了 bUnit 專案方便測試,不過 bUnit 並非框架而是專案,所以必須先建立三種測試框架之一的專案,再去 NuGet 下載 bUnit。
首先在方案底下建立測試專案 BlazorServerMsTest,這邊筆者用 MSTest 框架。建立好後在專案名稱上點兩下開啟 csproj 檔案,會看到 Sdk 為 "Microsoft.NET.Sdk",要改成 "Microsoft.NET.Sdk.Razor",否則「Blazor 編譯器」不會渲染 Razor Component;TargetFramework 則要改成 net6.0,這樣才能跟我們的 BlazorServer 專案相容。



接著去 NuGet 下載 bunit,並且引用主專案 BlazorServer。如果要測試的 Component 沒有用到資料,這樣就完成前期作業了。但現實情況都是需要跟資料互動,也就是需要 Service,所以要下載可以「產生假資料」的 Service,筆者用的是 NSubstitute。

(**註:**如果想把測試方法都寫在 razor 檔案的 @code 區塊,就需要在 _Import.razor 放置用到的 namespace,但因為筆者都用 code behind 的方式就不放了。)
單元測試中分為三個部分:Arrange、Act、Assert。Arrange 是指「測試前的準備」,Act 是「找出要測試的項目」,Assert 則是「測試的結果」。
我們來測試第一個 div.card 的 HTML 結果,先打開網頁去複製第一個有 card class 的內容,這邊的 User Id 會隨資料改變。

下圖的第 19 行建立了 bUnit 這個測試實體並賦值給 ctx,第 20 行則利用 NSubstitute 建立假的 IUserRepository,第 21 到 23 行呼叫了 GetUsersAsync() 不過丟了一個「假的 List()」,裡面只有一組 CustomUserViewModel,這邊故意給錯誤的資料;第 24 行「利用 DI 註冊 IUserRepository 服務」。

第 27 行「利用 bUnit 渲染出 UserManagement」並賦值給 cut (component under test),但如果要比較整個 UserManagement 要貼上很多 HTML 標籤,所以再用 Find() 找出第一個有 card class 的標籤。Find() 用的是 CSS 選擇器,代表可以放入標籤、class 或是 id 等等。
第 31 行之後就是用找出來的 element 跟我們放進 MarkupMatches() 的 HTML 標籤做比較。筆者這邊用的是多行,如果不想佔版面的人也可以全部濃縮成一行,bUnit 不會比較斷行。
接著就是要實際測試了,可以按 ctrl + r, a 或是從上方的測試頁籤找到 Test Explorer,然後就能看到測試失敗,會告訴你「實際的 HTML 跟預期的 HTML 差在哪裡」,因為我們給的假資料跟預期的不同,所以出錯了。


我們把假資料的 UserId 跟 UserName「改成跟預期的 HTML 資料一樣」,按下 ctrl r, t,就能看到通過測試了。


引用: