你的浏览器还没开启 Javascript 功能!

【読書メモ】The Art Of Unit Testing 2nd Edition - Chap2

先週から新しいプロジェクトに入りました。

そのプロジェクトでは一応単体テストはありますが、そもそもアーキテクチャ自体は階層化されていないため、クラス間の依存性が強い。

「これは実質結合テストじゃねぇ?」と思うことがしばしあります。

正直、私も正しいユニットテストの業務経験はないので、経験不足を先人たちの名著でカバーできればと思います。

というわけで、
The Art Of Unit Testing 2nd Editionの第2章をコードを書きつつ、大事な部分を説明していきます。

単体テストのフレームワーク

前回の単体テストのフレームワークを使わずにユニットテストコードを書きました。
実際ではフレームワーク使わないことはほぼありえません。

では、単体テストフレームワークのメリットはなにか?

  1. 繰り返しテストできる
  2. テストケースのカバレッジを計測できる
  3. テスト結果の比較、分析結果を手動でコード書かなくても、出力できる

フレームワークの種類

.NET に関しては少なくても3種類のテストフレームワークがあります。

  • MSTest
  • NUnit
  • xUnit

多くのプロジェクトでは盲目にMSTestを選択します。なぜなら、それはMicrosoftの製品だから。
私としては、それはテストフレームワーク選定の理由になりません。

メンバーの経験、フレームワークの特性・将来性・汎用性を鑑み、.NET での新規開発なら私は NUnit を推奨したいと思います。

NUnitは他の言語のテストフレームワーク(JavaのJUnit)から移植されたもので、共通する部分も多い。
また、MSTestにはない機能があります。

将来メンバーが他のプロジェクト(Java,PHP)でもNUnitの経験を活かせることが期待できます。

NUnitを使った初めての単体テスト

では、実際にコードを書きながら、NUnitの基本操作を覚えて行きたいと思います。

環境

項目バージョン
OSWindows 10 1909
SDK.NET Core 3.1
IDEVisual Studio 2019

SUT作成

まずはSUT(System Under Test)を作成します。

dotnet new classlib -n LogAn

LogAnalyzer.csを実装します。

using System;

namespace LogAn
{
    public class LogAnalyzer
    {
        public bool WasLastFileNameValid { get; set; }

        public bool IsvalidLogFileName(string filename)
        {
            WasLastFileNameValid = false;

            if (string.IsNullOrEmpty(filename))
            {
                throw new ArgumentException("filename has to be provided");
            }

            if (!filename.EndsWith(".SLF",
                StringComparison.CurrentCultureIgnoreCase))
            {
                return false;
            }

            WasLastFileNameValid = true;

            return true;
        }
    }
}

NUnitの作成

同じくdotnetコマンドでテストプロジェクト作成します。

dotnet new nunit -n LogAn.Tests

テストプロジェクトの命名規則は{SUTのプロジェクト名}.Testsです。

LogAn.Testsプロジェクトの参照にLogAnプロジェクトを追加してください。

テストコードの実装

ではクラスを作成します。
テストクラス名の命名規則は{SUTクラス名}+Test.csです。

今回の場合LogAnalyzerTest.csになります。

クラス作成完了したところで、テストメソッドを書きます。

メソッドの命名規則は{SUTのメソッド名}_{テスト条件}_{期待結果}です。

単体テストコード実装にあたって3Aのお作法があります。

  • Arrage : 準備(インスタンス生成、テストの前準備)
  • Act : SUTのオブジェクトを操作する
  • Assert : 検証(期待値になるかを検証する)
[Test]
public void IsvalidLogFileName_BadExtension_ReturnFalse()
{
    // Arrage
    var sutlogAnalyzer = new LogAnalyzer();

    // Act
    bool result = sutlogAnalyzer.IsvalidLogFileName("filewithbadextension.foo");

    // Assert
    Assert.False(result);
}

NUnitの各種アノテーション説明

Setup

テスト実行前に行う処理、SUTが同じインスタンスの場合、最初に生成することで後に書くテストメソッドの実装量が減る。でも、可読性を損なうので使わなくて済むなら使わなくていいです。

private LogAnalyzer sutlogAnalyzer = null;

[SetUp]
public void Setup()
{
    sutlogAnalyzer = new LogAnalyzer();
}

Category

テストの分類を書きます。どういう分類にするかはプロジェクトのルールに則ってください。
私は正常系・異常系で分類用にします。

[Test]
[Category("Normal")]
public void IsvalidLogFileName_GoodExtensionLowercase_ReturnTrue()
{
    bool result = sutlogAnalyzer.IsvalidLogFileName("filewithbadextension.slf");
    Assert.True(result);
}

// 例外テスト
[Test]
[Category("Abnormal")]
public void IsvalidFileName_EmptyFileName_Throws()
{
    var ex = Assert.Catch<ArgumentException>(() => sutlogAnalyzer.IsvalidLogFileName(""));

    // 想定通りのエラーメッセージが含まれることを検証
    StringAssert.Contains("filename has to be provided", ex.Message);
}

TestCase

NUnitの魅力はなんと行ってもこれ。TestCaseのアノテーションをつけることで、1メソッドで複数のテストケースを実行することができます。

アノテーションで引数を定義して、実行時に引数を用いてテストを実行できます。

[TestCase("filewithbadextension.SLF", true)]
[TestCase("filewithbadextension.slf", true)]
[TestCase("filewithbadextension.foo", false)]
public void IsvalidLogFileName_VariousExtension_CheksThem(string file, bool expected)
{
    bool result = sutlogAnalyzer.IsvalidLogFileName(file);
    Assert.AreEqual(expected, result);
}

ただし、上記のように汎用性のもたせすぎたTestCaseは可読性とケースがわかりずらくなるという理由から、あまり推奨できません。

Ignore

なにかの理由でテストコード書いたけど、テストをスルーさせたい場合に使います。

// テスト省略
[Ignore("there is a problem with this test")]
public void IsvalidLogFileName_VariousExtension_CheksThem(string file, bool expected)
{
    bool result = sutlogAnalyzer.IsvalidLogFileName(file);
    Assert.AreEqual(expected, result);
}

まとめ

今回は単体テストフレームワークを使ってテストコードを実装しました。

個人的にテストコードも美しさが必要です。テストコードの美しさの観点は・・・

  • テストプロジェクト、クラス、メソッド名からどういうテストしたいのかを読み取れる

につきると思います。どういう思いでテストコードを書いたのかを理解することにより、あとから来たメンバーはテスト不足がないかも把握できます。

とにかく、プロダクトコードもテストコードもなんとなくで書くのではなく、魂をこもって書きましょう。

出典

The Art of Unit Testing: with examples in C#

コード

Github Repository