@ledsun blog

無味の味は佳境に入らざればすなわち知れず

NUnitをつかってテストコードを書くときのテストメソッド名の命名規則

NUnitのテストの命名規則をどうするとわかりやすくなるのか悩ましいのでまとめてみます。

describe と context がない

NUnitにはRSpecとちがいdescribeとcontextに相当する機能がありません。 NUnitの公式のサンプルコードを見てみましょう。 https://github.com/nunit/nunit-csharp-samples/blob/master/money/MoneyTest.cs#L10

[TestFixture]
public class MoneyTest 
{
...
    [Test]
    public void IsZero() 
    {
        Assert.IsTrue(fMB1.Subtract(fMB1).IsZero);

        Money[] bag = { new Money(0, "CHF"), new Money(0, "USD") };
        Assert.IsTrue(new MoneyBag(bag).IsZero);
    }

JUnitから続く伝統のスタイルです。 MoneyクラスのテストをMoneyTest.csファイルのMoneyTestクラスに書きます。 IsZeroメソッドに対する特定の条件に対するテストメソッドをIsZeroメソッドで書きます。

RSpecではdescribeとcontextmをつかってテストファイルの中で領域をわけます。 RSpec Tutorial: Test-Drive Your Ruby Code - Semaphore のサンプルコードを見てみましょう。

# spec/string_calculator_spec.rb
describe StringCalculator do

  describe ".add" do
    context "given an empty string" do
      it "returns zero" do
        expect(StringCalculator.add("")).to eq(0)
      end
    end
  end
end

RSepcでは、StringCalculator クラスに対するテストはstring_calculator_spec.rbファイルに書きます。 addメソッドのテストはdescribeで区切ります。 テストの条件はcontextで区切ります。 describe/contextを使うことで、プロパティ言語上の名前空間ソースコード上の見た目の領域をわけています。 テストファイルが大きくなっても、ある程度の整理が可能です。

NUnitの方式では、次の情報がすべてテストメソッド名に押し込まれます。

  • テスト対象のメソッド名
  • テストの条件

このためテストメソッドの命名が難しいです。 テスト対象のクラスが複雑すぎるのがいけないのですが、複雑なクラスに後からテストを書くことが往々にしてあります。

テストクラスとテストメソッド名の工夫

マイクロソフトはこの問題に一定の解を示しています。 NUnit と .NET Core による単体テスト C# - .NET | Microsoft Learnに次のようなサンプルコードがあります。

namespace Prime.UnitTests.Services
{
    [TestFixture]
    public class PrimeService_IsPrimeShould
    {
        private PrimeService _primeService;

        [Test]
        public void IsPrime_InputIs1_ReturnFalse()
        {
            var result = _primeService.IsPrime(1);

            Assert.IsFalse(result, "1 should not be prime");
        }
    }
}

テストクラス名は PrimeService_IsPrimeShould です。 PrimeServiceクラスのIsPrimeShouldメソッドをテストするクラスです。 つまり、テストクラスをテスト対象のメソッドごとにわけます。

テストメソッド名は IsPrime_InputIs1_ReturnFalse です。 IsPrime メソッドに 1 を入力したら、Falseを返す。 つまりコンテキストと期待する値をメソッド名で示しています。

この作戦は良さそうに思います。

現在の僕のやり方

PrimeService_IsPrimeShould をクラス名にすると、つぎのようテスト対象のクラス名が並びます。

  • PrimeService_IsPrimeShould
  • PrimeService_HogeShould
  • PrimeService_FugaShould

そこでテスト対象のクラス名を名前空間にしました。

また、IsPrime_InputIs1_ReturnFalseも冗長に感じました。 つぎの変更をします。

  • テスト対象のメソッド名の省略
  • _区切りをWhenに変更

ReturnFalseWhenInput1になります。

namespace Prime.UnitTests.Services.PriveServiceTest
{
    [TestFixture]
    public class IsPrimeShould
    {
        private PrimeService _primeService;

        [Test]
        public void ReturnFalseWhenInput1()
        {
            var result = _primeService.IsPrime(1);

            Assert.IsFalse(result, "1 should not be prime");
        }
    }
}

つなげると PriveServiceTest.IsPrimeShould.ReturnFalseWhenInput1 です。 なかなか説明力の高い名前になったと、自画自賛しております。

このスタイルで統一はしていません。 JUnitスタイルで十分書けるテストは、JUnit スタイルで書いています。