ushibutatorism

がんばって言語化するぞ(カテゴリごちゃ混ぜです)

FactとTheoryの違いについて考える

FactとTheoryは何が違うのか?

具体的にはテストメソッドが引数を持てる/持てない、といった違いがあるが、なぜFactは引数を持てないのか?(なぜTheoryは引数を持てるのか?)について考えた。

Fact

  • 事実
  • テストコードには、始まりから終わりまで何が起こるか(事実)を記述する。
    • ストーリー、シナリオなどと言い換えてもいいかもしれない。
    • よって、外部から引数が渡されることはない(Factの中で完結する)。

Theory

  • 理論
  • テストコードには、抽象化された仕組み(理論)を記述する。
    • 法則、ルールなどと言い換えてもいいかもしれない。
    • よって、外部から引数を渡して理論通りに正しく動くかを検証する。

Theoryの方はテストコード自体がビジネスロジックの性質を持つのではないかと感じた。

ビジネスロジックを個別のクラスとして定義する場合はFactで記述する方が多いのかなと思う。 1つのFact=1つのテストシナリオ、というイメージか。

[Fact]
public void MyTest()
{
    var input = ...;

    var output = service.Method(input);

    Assert.Equal(...);
    // ...
}

一方で、ステートレスな演算をテストするような場合は、Theoryがよいかもしれない。

public class Point
{
    // ...

    public static Point operator +(Point a, Point b)
        => new Point(...);

    // ...
}

上記のような演算をテストしたい場合は

[Fact]
public void AddPoint()
{
    Assert.Equal(new Point(...), new Point(...) + new Point(...));
    Assert.Equal(new Point(...), new Point(...) + new Point(...));
    // ...
}

よりも

[Theory]
[MemberData(nameof(MyOperatorTestSources))]
public void AddPoint(Point a, Point b, Point c)
{
    Assert.Equal(c, a + b);
}

public static IEnumerable<object[]> MyOperatorTestSources()
{
    yield return new[] { new Point(...), new Point(...), new Point(...) };
    yield return new[] { new Point(...), new Point(...), new Point(...) };
    // ...
}

のほうがテストの意図を適切に表現できていると思う。

いずれにせよテストとして実施すべきことはどちらの属性でも実現できる。

どっちが読みやすいか、書きやすいか、テストケースの管理がしやすいか、というよりは、テストコードの性質・意図を説明する属性(まさに属性)である、と考えるのが適切だと思った。