関わっている案件が半年ほど前に変わり、単体テストを作りながら実装するようになって思ったことをまとめます。
そもそも単体テストとは
Googleで「単体テスト」で検索するとトップに出てきた記事の引用です。
単体テスト(ユニットテストと呼ばれることもあります)は、プログラムを構成する比較的小さな単位(ユニット)が個々の機能を正しく果たしているかどうかを検証するテストです。
通常、関数やメソッドが単体テストの単位(ユニット)となります。 プログラムが全体として正しく動作しているかを検証する結合テストは、開発の比較的後の段階でQAチームなどによって行なわれることが多いのとは対照的に、単体テストは、コード作成時などの早い段階で開発者によって実施されることが多いのが特徴です。
https://www.techmatrix.co.jp/t/quality/unittest.html
書いてみる
環境
- Visual Studio Code 1.40.0
- dotnet core 2.2
- Moq 4.13.1
- 拡張機能 .NET Core Test Explorer
テスト
public class TargetClassTest
{
[Fact(DisplayName = "名前が生成される")]
public void Test1()
{
var mockservice = new Mock<IService>();
mockservice.Setup(x => x.Exist(It.IsAny<string>())).Returns(false);
var target = new TargetClass(mockservice.Object);
var response = target.CreateName("hogehoge");
Assert.True(response == "Test.hogehoge");
}
[Fact(DisplayName = "登録済みのためエラー")]
public void Test2()
{
var mockservice = new Mock<IService>();
mockservice.Setup(x => x.Exist(It.IsAny<string>())).Returns(true);
var target = new TargetClass(mockservice.Object);
var ex = Assert.Throws<Exception>(() => { return target.CreateName("hogehoge"); });
Assert.True(ex.Message == "already exist.");
}
}
テスト対象
/// <summary>
/// テストするクラス
/// </summary>
public class TargetClass
{
private readonly IService service;
public TargetClass(IService service)
{
this.service = service;
}
public string CreateName(string name)
{
if (service.Exist(name) == true)
throw new Exception("already exist.");
var result = $"Test.{name}";
return result;
}
}
/// <summary>
/// Injectionされるサービス
/// </summary>
public interface IService
{
bool Exist(string name);
}
要点
- アトリビュートでテストの内容がファイルを開かなくてもわかるように記述
- DIされるサービスはMockを使用してテスト項目は対象クラスのみに限定する
- Assertでレスポンスが期待値か確認する
- Exceptionを発生させる場合は、Throwsで何のExceptionか指定して受け取り、Message等を確認する
最後にテストの必要性
「対価」の有無で決まると思います。
クライアントがいて納品が発生するならテストは必要でしょうし、自身の業務の効率化・自動化のためのツール程度であれば不要。
(後者はテストしたいならしてもいいと思います)
テストは引用の中にもあるように「機能を正しく果たしているかどうか検証する」ことを目的としているので、
「テスト」≒「設計」 であることを忘れないようにしたいです。