Visual Studio 2017 測試專案兩三事

在寫單元測試的時候,我注意到 Visual Studio 專案範本產生的 .csproj 有的是新格式,有的是舊格式。在動手修改成新格式的過程中,東試西試,便有了這篇筆記。

摘要:
  • 使用 Visual Studio 2017 來分別建立三種單元測試框架(MSTest, NUnit, xUnit)的測試專案,並比較 .NET Core 與非 .NET Core 專案的差異。
  • 沒有要比較單元測試的寫法(例如 [TestMethod] vs. [Test] vs. [Fact])。
  • 使用 Package Reference 取代舊的 Packages.config,以及 Visual Studio 2017 的相關設定(別漏掉這個)。
  • 新的 csproj 格式才是王道。
    (跟舊 .csproj 格式比起來,新格式真是簡潔到讓我想立刻擺脫所有舊版 .csproj 格式)

開發環境


以 .NET Framework 為目標平台的測試專案


如下圖所示:


若選擇圖中的 Unit Test Project (.NET Framework) 範本,代表你打算使用 MSTest 測試框架,而且:
  • 目標平台會是 .NET Framework,而非 .NET Core。
  • .csproj 檔案是舊格式。
  • 該專案會使用 packages.config 檔案來紀錄參考的套件。這是舊格式,稍後會提到新格式。

圖中還有一個 NUnit 3 Unit Test Project 範本。若選擇這個,測試框架就是 NUnit 3,而且跟剛才一樣:
  • 目標平台會是 .NET Framework,而非 .NET Core。
  • .csproj 檔案是舊格式。
  • 該專案會使用舊的 packages.config 檔案來紀錄參考的套件。

當然,你可以透過手動修改 .csproj 的方式來改變目標平台、把 csproj 改成新格式、以及使用新的 Package Reference 來取代 packages.config 檔案。這些手工程序我試過,不難,就是瑣碎。

以 .NET Core 為目標平台的測試專案


如果要以 .NET Core 作為測試專案的目標平台,比較簡便的方法是直接挑選 .NET Core 分類底下的專案範本:


在上圖中,依我們想要使用的測試框架而定,有下列選擇:
  • Class Library (.NET Core):使用 NUnit 或其他測試框架時,都可以選這個。
  • MSTest Test Project (.NET Core):使用 MSTest 選這個最直接。
  • xUnit Test Project (.NET Core):使用 xUnit 就選這個。
註:在上圖的對話框裡,如果在左邊面板選擇 .NET Standard,就沒有任何測試專案範本可選。這是因為,微軟認為測試專案一定是針對特定的具體平台來執行,例如 .NET Framework 4.7 或 .NET Core 2.0;而  .NET Standard 是一份合約,故不能作為測試專案的目標平台。如果你跟我一樣好奇,可以試試看手動修改測試專案的 .csproj 檔案,把目標平台改為 netstandard2.0 看看會發生什麼事。我記得建置專案或執行測試時會出錯。

以上述方式建立的測試專案,無論選擇哪個範本,都是用比較新的設定和格式:
  • 目標平台是 .NET Core (netcoreapp2.0)。
  • .csproj 檔案是簡潔乾淨的新格式(檔案內容的第一行是 <Project Sdk="Microsoft.NET.Sdk">)
  • 在 .csproj 檔案中使用 Package Reference 來紀錄參考的套件(沒有以前的 packages.config 檔案了)

比較特別的是 NUnit,因為我的開發環境並沒有專給 .NET Core 平台使用的 NUnit 3 專案範本(有發現這個:dotnet-new-nunit,但沒去試),而只是使用一般的 Class Library 專案範本,所以我們還得自行加入一些套件參考:
  • Microsoft.NET.Test.Sdk (目前的版本是 15.7.0)
  • NUnit (目前的版本是 3.10.1)
  • NUnit3TestAdapter (目前的版本是 3.10.0)

關於 Package Reference 格式


剛才提到,以 .NET Core 為目標平台的專案,都是使用新的 .csproj 格式,而且在管理 NuGet 套件方面,也是採用新的 Package Reference 格式(取代舊的 Packages.config 檔案)。

這裡再補充說明兩點。

非 .NET Core 專案也可以使用 Package Reference 來管理套件嗎?

可以。只要 Visual Studio 2017 有安裝 Update 1 以後的版本,我們就可以從主選單的 Tools | Options 看到新的選項:


按照上圖設定好之後,當你為一個新專案(目標平台不是 .NET Core)第一次加入套件時,Visual Studio 就會問你要使用哪一種套件管理格式:


另外,就算既有專案已經使用 packages.config,我們也可以把那個檔案刪掉,然後在 Visual Studio 中開啟專案,加入套件,此時仍然會跳出上面的對話窗讓你選擇使用新的 Package Reference 格式。

舊的 csproj 格式能夠使用 Package Reference 嗎?

可以。你可以在 .csproj 檔案的第一個 <PropertyGroup> 區塊裡面加上這行:

<RestoreProjectStyle>PackageReference</RestoreProjectStyle>

這樣就能讓舊格式的 .csproj 專案改用 Package Reference 的方式來管理套件。

產生單元測試程式碼


剩下的工作,就是撰寫單元測試了。這個部分,我是用 Unit Test Boilerplate Generator 來輔助產生測試程式碼。

用法很簡單,只要在 Solution Explorer 中對受測程式碼的 .cs 檔案點一下滑鼠右鍵,然後選擇 Create Unit Test Boilerplate...,就會開啟一個對話窗,讓你選擇要產生哪一種測試框架的程式碼:


在這個對話窗裡,你可以選擇:
  • 把測試程式碼產生在哪一個測試專案。
  • 使用哪一種測試框架。選項有三個:Visual Studio(即 MSTest)、NUnit、xUnit。
  • 使用哪一種 mock 框架。選項有:Moq、AutoMoq、SimpleStubs、NSubstitute、和 Rhino Mocks。

.NET Core、NUnit、與 Live Unit Testing


還要提醒一件事,仍然跟 NUnit 有關。

NUnit 文件裡面提到,使用 NUnit 作為測試框架時,若測試專案的目標平台是 .NET Core,那麼 Visual Studio 的 Code Coverage 和 Live Unit Testing 功能會無法正常運行。文件中還說,可能等到 Visual Studio 2017 15.3 版以後,Live Unit Testing 就可以正常執行 NUnit
測試了。

可是我的 Visual Studio  是 15.6 版,當我的應用程式專案的目標平台是 .NET Standard 2.0 時,每當建置測試專案, Output 視窗還是會出現像底下的錯誤訊息(也許您的環境不會這樣?請留言指點):

Build completed (succeeded).
[TestDiscoverer 1] System.IO.FileNotFoundException: D:\Work\....\Debug\netstandard2.0\MyClassLib.dll 中沒有可用的測試。請確定測試專案具有套件 "Microsoft.NET.Test.Sdk" 的 NuGet 參考,且架構的版本設定適當,然後再試一次。
   於 Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Hosting.DotnetTestHostManager.GetTestHostProcessStartInfo(IEnumerable`1 sources, IDictionary`2 environmentVariables, TestRunnerConnectionInfo connectionInfo)
   於 Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.ProxyOperationManager.SetupChannel(IEnumerable`1 sources, CancellationToken cancellationToken)
   於 Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.ProxyDiscoveryManager.DiscoverTests(DiscoveryCriteria discoveryCriteria, ITestDiscoveryEventsHandler2 eventHandler)
[03:00:53.711 Error] 測試探索已中止/逾時。

不過,在 Test Explorer 視窗裡面執行測試則完全正常。所以那個在背景執行的即時單元測試所產生的錯誤訊息,倒也沒有什麼妨礙,不看它就好了。

使用命令列工具來執行測試


我目前大多還是在 IDE 裡面操作各項功能,因為我很少發現需要切換到命令列視窗去下指令的情況。不過,這年頭少了命令列,似乎就少了點什麼。 😊

指令很簡單:

dotnet test 測試專案名稱.csproj

執行結果如下圖:


結語


最終我選擇了 NUnit,但這並非本文重點(MSTest、NUnit、和 xUnit 都好)。我在意的是測試專案的 .csproj 檔案內容,像這樣:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netcoreapp2.0</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" />
    <PackageReference Include="NUnit" Version="3.10.1" />
    <PackageReference Include="NUnit3TestAdapter" Version="3.10.0" />
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\..\src\MyLibrary.csproj" />
  </ItemGroup>
</Project>

跟舊的 .csproj 格式相比,實在是乾淨得不像話 XD

然而,新格式的優點不是只有簡潔而已。在某些情況下,它還能避免組件引用所衍生的麻煩問題。正如 Hanselman 在他的文章裡引用 Oren 的話:
「Using .NET Standard requires you to use PackageReference to eliminate the pain of  "lots of packages"  as well as properly handle transitive dependencies. While you may be able to use .NET Standard without PackageReference, I wouldn't recommend it.」 
簡單地說,使用新的 csproj 格式和 Package Reference 才是王道。

Happy coding!

沒有留言:

技術提供:Blogger.
回頂端⬆️