個人でプログラミングを楽しむ分にはどのような方法で開発しても構わないと思いますが、すこし規模が大きくなると、クラス単位に機能や役割を分割することになり、それらを単体テストする必要性を感じることがあります。
今回は、テストプロジェクトの作成~単体テストの実行までの流れをざっくり把握したいと思います。
プロジェクト作成
単体テストのプロジェクトを作るにあたって、複数のプロジェクトを扱うことに成りますのでソリューションを作成します。
PowerShell
mkdir MSTestSample01
cd MSTestSample01
dotnet new sln
” MSTestSample01″がソリューション名に成ります。
ソリューションに単体テストを含むプロジェクトを登録する形になります。またソリューション用のディレクトリを作成しましたので、こちらにサブディレクトリとしてプロジェクト用のディレクトリを作成していくことになります。
被テスト対象となるプロジェクトを作成します。多分プロジェクトの種類は何でも良いのだと思うのですが、WinFormやWPFのViewに相当する部分はMSTestによる単体テストは難しいと思うので、テストしやすそうなシンプルなクラスということでClassLib(クラスライブラリ)でプロジェクトを作成します。
PowerShell
mkdir MSTestSample01.Infra
cd MSTestSample01.Infra
dotnet new classlib
プロジェクト名は”MSTestSample01.Infra”になります。
プロジェクトをソリューションに登録します。
cd ..
dotnet sln add MSTestSample01.Infra/MSTestSample01.Infra.csproj
次に単体テスト(MSTest)のプロジェクトを作成します。
PowerShell
mkdir MSTestSample01Tests
cd MSTestSample01Tests
dotnet new mstest
cd ..
dotnet sln add MSTestSample01Tests/MSTestSample01Tests.csproj
プロジェクト名は”MSTestSample01Tests”になります。
ディレクトリツリーは以下の様になりました。
フォルダー パスの一覧
MSTestSample01
│ MSTestSample01.sln
│ tree.txt
│
├─MSTestSample01.Infra
│ │ Class1.cs
│ │ MSTestSample01.Infra.csproj
│ │
│ └─obj
│ MSTestSample01.Infra.csproj.nuget.dgspec.json
│ MSTestSample01.Infra.csproj.nuget.g.props
│ MSTestSample01.Infra.csproj.nuget.g.targets
│ project.assets.json
│ project.nuget.cache
│
└─MSTestSample01Tests
│ MSTestSample01Tests.csproj
│ UnitTest1.cs
│
└─obj
MSTestSample01Tests.csproj.nuget.dgspec.json
MSTestSample01Tests.csproj.nuget.g.props
MSTestSample01Tests.csproj.nuget.g.targets
project.assets.json
project.nuget.cache
テストをするため、MSTestSample01TestsからMSTestSample01.Infraを参照する設定を行います。
PowerShell
cd MSTestSample01Tests
dotnet add reference ../MSTestSample01.Infra/MSTestSample01.Infra.csproj
これでテストを行うプロジェクトが作成されました。
単体テスト
続いてテストコードを書いていきます。
ファイル名:MSTestSample01.Infra/Class1.cs
namespace MSTestSample01.Infra;
public class Class1
{
// 加算
public static int Add(int a, int b)
{
return a + b;
}
}
こちらは、テストされる対象のクラスで、テストしやすいようにstaticな加算するメソッドを用意しました。インスタンスを作成する必要が無いですし、メソッド内で完結している点がテストしやすいと考えます。
ファイル名:MSTestSample01Tests/UnitTest1.cs
using MSTestSample01.Infra;
namespace MSTestSample01Tests;
[TestClass]
public class UnitTest1
{
[TestMethod]
public void 加算のテスト()
{
int a = 2;
int b = 3;
int actual = Class1.Add(a, b); // ... 実際の結果
int expected = 5; // ... 期待される値
Assert.AreEqual(expected, actual);
}
}
こちらはテストする側のコードに成ります。
UnitTest1のコンストラクタは?とか[TestMethod]
属性はどんな意味がある?など疑問がわきますが、とりあえずこれでテストを実行することが出来ます。
あと人が呼び出す予定がないのでテストメソッドの名は日本語を使っています。
テストの実行はソリューションディレクトリで以下のコマンドを実行します。
dotnet test
Determining projects to restore...
H:\csharp\console\MSTestSample01\MSTestSample01Tests\MSTestSample01Tests.csproj を復元しました (428 ミリ秒)。
2 個中 1 個の復元対象のプロジェクトは最新です。
MSTestSample01.Infra -> H:\csharp\console\MSTestSample01\MSTestSample01.Infra\bin\Debug\net8.0\MSTestSample01.Infra.d
ll
MSTestSample01Tests -> H:\csharp\console\MSTestSample01\MSTestSample01Tests\bin\Debug\net8.0\MSTestSample01Tests.dll
H:\csharp\console\MSTestSample01\MSTestSample01Tests\bin\Debug\net8.0\MSTestSample01Tests.dll (.NETCoreApp,Version=v8.0) のテスト実行
Microsoft (R) Test Execution Command Line Tool Version 17.9.0 (x64)
Copyright (c) Microsoft Corporation. All rights reserved.
テスト実行を開始しています。お待ちください...
合計 1 個のテスト ファイルが指定されたパターンと一致しました。
成功! -失敗: 0、合格: 1、スキップ: 0、合計: 1、期間: 43 ms - MSTestSample01Tests.dll (net8.0)
多分成功していると思うので、コードを書き替えてわざと失敗してみます。
変更したソース
using MSTestSample01.Infra;
namespace MSTestSample01Tests;
[TestClass]
public class UnitTest1
{
[TestMethod]
public void 加算のテスト()
{
int a = 2;
int b = 3;
int actual = Class1.Add(a, b); // ... 実際の結果
int expected = 4; // ... 期待される値
Assert.AreEqual(expected, actual);
}
}
こちらでテストを実行してみます。
dotnet test
Determining projects to restore...
復元対象のすべてのプロジェクトは最新です。
MSTestSample01.Infra -> H:\csharp\console\MSTestSample01\MSTestSample01.Infra\bin\Debug\net8.0\MSTestSample01.Infra.d
ll
MSTestSample01Tests -> H:\csharp\console\MSTestSample01\MSTestSample01Tests\bin\Debug\net8.0\MSTestSample01Tests.dll
H:\csharp\console\MSTestSample01\MSTestSample01Tests\bin\Debug\net8.0\MSTestSample01Tests.dll (.NETCoreApp,Version=v8.0) のテスト実行
Microsoft (R) Test Execution Command Line Tool Version 17.9.0 (x64)
Copyright (c) Microsoft Corporation. All rights reserved.
テスト実行を開始しています。お待ちください...
合計 1 個のテスト ファイルが指定されたパターンと一致しました。
失敗 加算のテスト [71 ms]
エラー メッセージ:
Assert.AreEqual に失敗しました。<4> が必要ですが、<5> が指定されました。
スタック トレース:
at MSTestSample01Tests.UnitTest1.加算のテスト() in H:\csharp\console\MSTestSample01\MSTestSample01Tests\UnitTest1.cs:line 17
at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
失敗! -失敗: 1、合格: 0、スキップ: 0、合計: 1、期間: 148 ms - MSTestSample01Tests.dll (net8.0)
失敗したのでテストプロジェクトは機能しているようんです。
あとはテストコードを書いて、テストされるメソッドやプロパティを書いてを繰り返すことに成ります。
Assertメソッドの一覧
使いそうなAssertメソッドの一覧に成ります。
<
dl>
Assert.AreEqual(expected, actual)
Assert.AreNotEqual(notExpected, actual)
Assert.IsTrue(condition)
Assert.IsFalse(condition)
Assert.AreSame(expected, actual)
Assert.AreNotSame(expected, actual)
Assert.IsNull(value)
Assert.IsNotNull(value)
Assert.IsInstanceOfType(value, expectedType)
Assert.IsNotInstanceOfType(value, wrongType)
Assert.ThrowsException<T>(action)
感想
ここまでの試してみて、テストしやすいクラスとそうで無いクラスがある感じです。
最初に述べたGUIアプリのView部分は単体テストを行う方法が思いつきません。逆にコントロールなどのView特有の部分を切り離したViewModelやModel部分は単体テストが出来ると思います。
また、テスト用にデータベースなど構築することが難しいオブジェクトに依存したクラスをテストする場合、スタブ(データ)やモック(振る舞い)などのダミーオブジェクトを用意してテストを行うことが出来るようです。モックを作成するモジュールがあるのでそのうち試したいと思います。
コメント