 
C#による.NET Core入門(7)
.NET Coreで単体テストを行う
クロスプラットフォームで開発できる.NET Coreの基礎から開発実践までが学べる入門連載。最終回は単体テスト用のプロジェクトを作成して、テストを行う方法を説明する。
.NET Coreと単体テスト
.NET Coreで利用可能な単体テストツール
.NET Coreは単体テストの実行も容易になるように設計されており、.NET Core CLIで単体テストプロジェクトの作成から実行まで行えるようになっている。単体テストフレームワークとして、xUnit、NUnit、MSTestがサポートされている。また、言語としてもC#、F#、Visual Basicのいずれも、これら3つのフレームワークで利用可能だ。
今回は、C#のクラスライブラリプロジェクトに、これら3つのフレームワークを利用した単体テストプロジェクトを作成しながら、簡単な使い方を説明しよう。なお、今回は同じソリューションに異なる3つのフレームワークを使った単体テストプロジェクトを追加する。通常の利用では1つのソリューションに対し、利用するテストフレームワークは1つであると思われるが、このような構成もできるということで参考にしてほしい。
また、今回の連載では各テストフレームワークの詳細な機能は説明せず、.NET Core CLIから利用する場合の基本的な使い方のみ説明する。詳細な機能や、それぞれの機能比較については用途に応じて行ってほしい。
単体テストプロジェクトを作成する前に、テストする対象のプロジェクトをソリューションファイルと併せて作成しておこう。リスト1のコマンドでUnitTestingというディレクトリを作成し、ソリューションファイルを作成、その下にMyServiceというディレクトリを作成し、クラスライブラリプロジェクトを作成して、ソリューションに追加している。dotnet newコマンド実行時に名前を指定していないので、ディレクトリ名がソリューションやプロジェクトの名前に使われる。
| $ mkdir UnitTesting $ cd UnitTesting $ dotnet new sln $ mkdir MyService $ cd MyService $ dotnet new classlib $ cd .. $ dotnet sln add MyService/MyService.csproj | 
ソリューションファイルを作成したディレクトリをVisual Studio Codeで開くと、以降で追加するプロジェクトを同時に編集できる。開いた後、MyService/Class1.csファイルをMyService/Calculator.csにリネームして、リスト2のようにテスト対象のクラスとメソッドを作成してほしい。
| using System; namespace MyService {   public class Calculator   {     public bool IsEven(int number)     {       throw new NotImplementedException("Not Yet");     }   } } | 
xUnitの利用
xUnitは、NUnitもそうだがオープンソースのテストフレームワークで、.NET Framework向けにリリースされ、.NET Coreにも対応している。xUnitの単体テストプロジェクトを作成するためのプロジェクトテンプレートはデフォルトで.NET Core CLIに含まれているため、すぐにプロジェクトを作成してテストを書き始めることができる。
それでは、リスト1実行後のソリューションファイルのあるディレクトリをカレントディレクトリとして、リスト3のコマンドを実行してほしい。xUnitの単体テストプロジェクトを作成し、テストコード対象となるプロジェクトを参照として追加し、ソリューションに追加している。
| $ mkdir MyService.xTests $ cd MyService.xTests $ dotnet new xunit $ dotnet add reference ../MyService/MyService.csproj $ cd .. $ dotnet sln add MyService.xTests/MyService.xTests.csproj | 
 dotnet new xunitでxUnitの単体テストプロジェクトを作成できる。作成されたプロジェクトのプロジェクトファイル、リスト3の場合はMyService.xTests.csprojを開くとリスト4のようになっているだろう。なお、リスト4はリスト3にあるdotnet add referenceコマンドを実行した後の状態であるため、テスト対象プロジェクトへの参照がすでに追加されている。
| <Project Sdk="Microsoft.NET.Sdk">   <PropertyGroup>     <TargetFramework>netcoreapp2.0</TargetFramework>     <IsPackable>false</IsPackable>   </PropertyGroup>   <ItemGroup>     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0" />     <PackageReference Include="xunit" Version="2.3.1" />     <PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />     <DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />   </ItemGroup>   <ItemGroup>     <ProjectReference Include="..\MyService\MyService.csproj" />   </ItemGroup> </Project> | 
 まず、<PropertyGroup>で<IsPackable>がfalseなのでこのプロジェクト自体をパッケージとすることはできないが、<TargetFramework>がnetcoreapp2.0となっている。netstandard2.0でないのは、テストツールが必要とするパッケージの違いからである。またテストツールの記述と実行に必要なパッケージが<PackageReference>と<DotNetCliToolReference>に記載されている。テストコードを実行する処理もこのパッケージに含まれているため、このテスト単体プロジェクトではテストクラスの作成のみ行えば、そのテストクラスを実行してくれる。
 それでは実際にテストコードを書いてみよう。xUnitの最も簡単なテストメソッドの作成は、テストメソッドとなるメソッドにFact属性を付与する方法である。UnitTest1.csというファイルが生成されているはずなので、Calculator_IsEvenShould.csというファイル名に変更し、リスト5のように記述してほしい。
| using System; using Xunit; namespace MyService.xTests {   public class Calculator_IsEvenShould   {     private readonly Calculator calculator;     public Calculator_IsEvenShould()     {       calculator = new Calculator();     }     [Fact]     public void ReturnFalseGivenValueOf1()     {       var result = calculator.IsEven(1);       Assert.False(result, "1 should not be even");     }   } } | 
テストメソッドを記述するクラスのコンストラクターで、テストを行う対象のクラスのインスタンスを生成している。テストメソッド内で、そのインスタンスに対してメソッドを実行し、結果が想定通りかテストを行っている。
当然リスト2の実装では、例外がスローされるためテストは失敗するはずだが、実際に実行してみよう。
 テストプロジェクトのプロジェクトファイルがあるディレクトリをカレントディレクトリにして、dotnet testコマンドを実行すると、テストが実行される。なお、カレントディレクトリがソリューションファイルのあるディレクトリの場合、全プロジェクトに対して順次実行するが、テストプロジェクトでないプロジェクトに対してもテストを実行しようとし、「テストプロジェクトでないため、実行できない」というエラーメッセージが表示されるので注意してほしい。
 図1に実際にdotnet testコマンドを実行した様子を載せている。例外がスローされたことによりテストが失敗していることが分かる。環境によっては、英語ではなく、日本語でメッセージが表示されるかもしれない。
それでは、テスト対象のメソッドを正しく実装し、テストメソッドも追加してみることにしよう。まず、テスト対象のメソッドだが、リスト6のように修正する。
| public bool IsEven(int number) {   return number % 2 == 0; } | 
 続いて、テストクラスCalculator_IsEvenShouldにリスト7のメソッドを追加しよう。このメソッドは引数を持っている。複数のテストデータに対して同一の結果を期待するテストケースの場合、Theory属性を付け、テストデータを指定したInlineData属性をテストデータの種類の数だけ付ける。FactとTheoryという名前の由来はxUnitのドキュメントを参考にしてほしい。
| [Theory] [InlineData(0)] [InlineData(2)] [InlineData(100)] public void ReturnTrueGivenValue0_2_100(int value) {   var result = calculator.IsEven(value);   Assert.True(result, $"{value} should be even"); } | 
 この状態でdotnet testを実行した結果が図2である。Fact属性のテストメソッド1つと、Theory属性のテストメソッドがInlineData属性3つ分、合わせて4つのテストを実行し、全てパスしたことが分かる。
 ここまで書いた状態でVisual Studio Codeでテストメソッドを記述した部分を見てみると、run test、debug testというリンクが表示されているのが分かる(※もし表示されない場合は、Visual Studio Codeを再起動してほしい)。このリンクをクリックすると、それぞれ該当のテストメソッドの実行もしくはデバッグ実行が始まる。記述したテストメソッドをまず実行したいとき、もしくは想定通り動かないのでデバッグ実行したいときは、ここから実行できる。
NUnitの利用
NUnitは、JavaのテスティングフレームワークJUnitを.NET Frameworkに移植したもので、JUnitを使ったことのある人にはなじみのある構文で単体テストを記述できる。NUnitに関してはデフォルトの.NET Core CLIにテンプレートが含まれていないため、まずはテンプレートを取得する必要がある。リスト8の手順でインストールしてほしい(※ユーザー単位で保存されるので、カレントディレクトリはどこでもよい)。
| $ dotnet new -i NUnit3.DotNetNew.Template | 
一度インストールすれば、以降はプロジェクトのテンプレートとして選択可能になる。リスト9の手順で、同じソリューションにNUnit単体テストプロジェクトを作成し追加している。
| $ mkdir MyService.nTests $ cd MyService.nTests $ dotnet new nunit $ dotnet add reference ../MyService/MyService.csproj $ cd .. $ dotnet sln add MyService.nTests/MyService.nTests.csproj | 
作成されたプロジェクトファイルを見ると、xUnit同様、必要なライブラリが追加されていることが分かる。
 それではNUnitのテストを記述してみよう。xUnitのリスト6/7と同等のものをリスト10に記載した(ファイル名もCalculator_IsEvenShould.csと同じにした)。NUnitの場合、テストメソッドにはTest属性を追加し、テストデータを引数として渡す場合はテストデータを引数に指定したTestCase属性を必要なテストデータの数だけ追加する。
| using MyService; using NUnit.Framework; namespace Tests {   public class Calculator_IsEvenShould   {     private readonly Calculator calculator;     public Calculator_IsEvenShould()     {       calculator = new Calculator();     }     [Test]     public void ReturnFalseGivenValueOf1()     {       var result = calculator.IsEven(1);       Assert.False(result, "1 should not be even");     }     [TestCase(0)]     [TestCase(2)]     [TestCase(100)]     public void ReturnTrueGivenValue0_2_100(int value)     {       var result = calculator.IsEven(value);       Assert.True(result, $"{value} should be even");     }   } } | 
 テストの実行は同じくdotnet testでよい。実行した様子を図4に載せた。ソリューションファイルのディレクトリで実行すると、全てのプロジェクトに対して実行されるのでxUnitとNUnitの両方のテストが実行される。
MSTestの利用
xUnitおよびNUnitに加え、MSTestも.NET Core CLIに統合されたテスティングツールとして利用可能だ。デフォルトで.NET Core CLIに含まれているので、リスト11の手順でMSTest単体テストプロジェクトを作成し、ソリューションに追加してみよう。
| $ mkdir MyService.msTests $ cd MyService.msTests $ dotnet new mstest $ dotnet add reference ../MyService/MyService.csproj $ cd .. $ dotnet sln add MyService.msTests/MyService.msTests.csproj | 
 MSTestの単体プロジェクトも、プロジェクトファイルに必要なライブラリが追加されている。MSTestで今までと同様のテストクラスをリスト12に載せた(ファイル名もCalculator_IsEvenShould.csと同じにした)。MSTestの場合、まず単体テストのクラスにTestClass属性を追加する。テストメソッドにはTestMethod属性を追加し、テストデータを引数で取るテストメソッドはDataTestMethod属性を追加したうえでテストデータを引数に指定したDataRow属性を必要なテストデータの数だけ追加する。
| using Microsoft.VisualStudio.TestTools.UnitTesting; namespace MyService.msTests {   [TestClass]   public class Calculator_IsEvenShould   {     private readonly Calculator calculator;     public Calculator_IsEvenShould()     {       calculator = new Calculator();     }     [TestMethod]     public void ReturnFalseGivenValueOf1()     {       var result = calculator.IsEven(1);       Assert.IsFalse(result, "1 should not be even");     }     [DataTestMethod]     [DataRow(0)]     [DataRow(2)]     [DataRow(100)]     public void ReturnTrueGivenValue0_2_100(int value)     {       var result = calculator.IsEven(value);       Assert.IsTrue(result, $"{value} should be even");     }   } } | 
 テストプロジェクトの実行も、同じくdotnet testでよい。図5に実行結果を載せた。
■
今回は.NET Coreで利用可能な単体テストとして、.NET Core CLIと統合されているxUnit、NUnit、MSTestの簡単な使い方を説明した。実際のプロジェクトで利用される場合は、より便利なテストメソッドの記述法や実行方法があるので、ぜひそれぞれのツールのドキュメントを参照してほしい。
「Linux/Macユーザーも、.NET Core CLIとVisual Studio Codeを使うことで、.NET Coreの開発を始めてほしい」という趣旨の連載も、今回で最終回となる。少しでも.NET Coreの開発を始めるきっかけになったら幸いである。
※以下では、本稿の前後を合わせて5回分(第3回~第7回)のみ表示しています。
 連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。
 
3. .NET Coreでプロジェクトを作成して開発してみよう
クロスプラットフォームで開発できる.NET Coreの基礎から開発実践までが学べる入門連載。3回目は実際にプロジェクトを新規に作成して、Visual Studio Codeを使って開発するフローを説明する。
 
4. .NET Coreでコンソールアプリを配置する
クロスプラットフォームで開発できる.NET Coreの基礎から開発実践までが学べる入門連載。4回目は作成したコンソールアプリのプロジェクトをビルドして配置する手順を説明する。
 
5. .NET Standardなライブラリプロジェクトを作成して参照する
クロスプラットフォームで開発できる.NET Coreの基礎から開発実践までが学べる入門連載。5回目は.NET Standardなクラスライブラリなプロジェクトを作成し、別のコンソールプロジェクトから参照する方法を説明する。
 
6. .NET CoreライブラリプロジェクトをパッケージングしてNuGetサーバーに発行する
クロスプラットフォームで開発できる.NET Coreの基礎から開発実践までが学べる入門連載。6回目はクラスライブラリプロジェクトをNuGetパッケージとして参照できるように、作成と発行を行う。





 
  
  
  
  
 