何も返さない純粋なメソッドのテストを作成するにはどうすればよいですか?


13

値の検証を扱うクラスがたくさんあります。たとえば、RangeValidatorクラスは値が指定された範囲内にあるかどうかをチェックします。

すべてのバリデータクラスには2つのメソッドが含まれます:値is_valid(value)を返すTrueFalse、値に応じて、ensure_valid(value)指定された値をチェックし、値が有効な場合は何もしないか、値が事前定義されたルールに一致しない場合は特定の例外をスローします。

現在、このメソッドに関連する2つの単体テストがあります。

  • 無効な値を渡し、例外がスローされたことを確認するもの。

    def test_outside_range(self):
        with self.assertRaises(demo.ValidationException):
            demo.RangeValidator(0, 100).ensure_valid(-5)
    
  • 有効な値を渡すもの。

    def test_in_range(self):
        demo.RangeValidator(0, 100).ensure_valid(25)
    

2番目のテストは、例外をスローすると失敗し、ensure_valid何もスローしないと成功しますが、assert内部にs がないという事実は奇妙に見えます。そのようなコードを読んだ人は、何もしていないように見えるテストがなぜあるのかをすぐに自問するでしょう。

これは、値を返さず、副作用のないメソッドをテストするときの現在のプラクティスですか?または、テストを別の方法で書き直す必要がありますか?または、単に私がやっていることを説明するコメントを入れますか?



11
肝心な点ですが、引数をとらない(self参照のために保存する)関数があり、結果を返さない場合、それは純粋な関数ではありません。
デビッドアルノ

10
@DavidArno:これは重要な点ではなく、問題の核心に直結します。メソッドは不純であるため正確にテストするのは困難です。
ヨルグW

@DavidArnoさらに重要な点として、「何もしない」メソッドを使用できます(「結果を返さない」はvoid、多くの言語で呼び出され、愚かなルールを持つ「ユニットタイプを返す」と解釈されます)。ループ(「結果を返さない」というのが実際に結果を返さないことを意味する場合でも機能します。)
Derek ElkinsはSEを去りました

ユニットを魔法のような特殊なケースではなく標準のデータ型とみなす言語には、型の純粋な関数が1つありunit -> unitます。残念ながら、ユニットを返すだけで、他には何もしません。
フォシ

回答:


21

ほとんどのテストフレームワークには、「投げない」という明示的なアサーションがあります。たとえば、Jasmineにはexpect(() => {}).not.toThrow();nUnitや友人にもあります。


1
また、テストフレームワークにそのようなアサーションがない場合、まったく同じことを行うローカルメソッドを作成して、コードを自明にすることが常に可能です。良いアイデア。
アルセニムルゼンコ

7

これは、使用する言語とフレームワークに大きく依存します。に関して言えば、方法NUnitがありAssert.Throws(...)ます。ラムダメソッドを渡すことができます。

Assert.Throws(() => rangeValidator.EnsureValid(-5))

これは内で実行されますAssert.Throwstry { } catch { }例外がキャッチされた場合、ラムダの呼び出しはブロックでラップされ、アサーションは失敗する可能性があります。

フレームワークがこれらの手段を提供していない場合、自分で呼び出しをラップすることで回避できます(私はC#で書いています)。

// assert that the method does not fail
try
{
    rangeValidator.EnsureValid(50);
}
catch(Exception e)
{
    Assert.IsTrue(false, $"Exception: {e.Message}");
}

// assert that the method does fail
try
{
    rangeValidator.EnsureValid(50);
    Assert.IsTrue(false, $"Method is expected to throw an exception");
}
catch(Exception e)
{
}

これにより、意図がより明確になりますが、コードがある程度乱雑になります。(もちろん、これらすべてをメソッドにラップすることができます。)最後は、あなた次第です。

編集

Doc Brownがコメントで指摘したように、問題はメソッドがスローすることを示すことではなく、スローしないことでした。NUnitには、そのためのアサーションもあります

Assert.DoesNotThrow(() => rangeValidator.EnsureValid(-5))

1

コメントを追加するだけで、アサートが不要な理由と、それを忘れなかった理由を明確にできます。

他の回答を見るとわかるように、他のものはコードをより複雑で煩雑にします。そこにコメントがあれば、他のプログラマはテストの意図を知るでしょう。

そうは言っても、この種のテストは例外である必要があります(しゃれはありません)。そのようなものを定期的に書いていることに気付いたら、おそらくテストは設計が最適ではないことを伝えているでしょう。


1

また、一部のメソッドが適切に呼び出される(または呼び出されない)と断言することもできます。

例えば:

public void SendEmail(User user)
     ... construct email ...
     _emailSender.Send(email);

テストで:

emailSenderMock.VerifyIgnoreArgs(service =>
    service.Send(It.IsAny<Email>()),
    Times.Once
);
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.