Assert.Fail() は悪い習慣とみなされますか?
-
02-07-2019 - |
質問
私は TDD を行うときに Assert.Fail をよく使います。私は通常、一度に 1 つのテストに取り組んでいますが、後で実装したいことのアイデアを思いついたときは、テスト メソッドの名前が実装したい内容を ToDo リストのようなものとして示す空のテストをすぐに作成します。忘れないように、本文に Assert.Fail() を追加しました。
xUnit.Net を試してみたところ、Assert.Fail が実装されていないことがわかりました。もちろん、いつでも Assert.IsTrue(false) を指定できますが、これでは私の意図が伝わりません。Assert.Fail は意図的に実装されていないという印象を受けました。これは悪い習慣とみなされますか?もしそうなら、なぜですか?
@Martin Meredithそれは私がしていることではありません。最初にテストを書いてから、それが機能するようにコードを実装します。通常、私は一度に複数のテストについて考えます。あるいは、別のことに取り組んでいるときに、書くテストのことを考えます。そのときは、覚えておくために空の失敗テストを作成します。テストを書き始めるまでに、きちんとテストファーストで作業します。
@jimmehそれは良い考えのように見えます。無視されたテストは失敗しませんが、別のリストに表示されます。それを試してみる必要があります。
@Matt Howells素晴らしいアイデア。この場合、NotImplementedException はassert.Fail() よりも意図を伝えます。
@mitch Wheatそれは私が探していたものです。私が乱用する別の方法で悪用されるのを防ぐために残されたようです。
解決
このシナリオでは、Assert.Fail を呼び出すのではなく、次のことを実行します (C# / NUnit で)
[Test]
public void MyClassDoesSomething()
{
throw new NotImplementedException();
}
これは Assert.Fail よりも明示的です。
Assert.Fail() よりも明示的なアサーションを使用することが好ましいという一般的な合意があるようです。ただし、ほとんどのフレームワークにはこれより優れた代替手段が提供されていないため、これを含める必要があります。たとえば、NUnit (およびその他) は、一部のコードが特定のクラスの例外をスローするかどうかをテストするために ExpectedExceptionAttribute を提供します。ただし、例外のプロパティが特定の値に設定されていることをテストするために、それを使用することはできません。代わりに Assert.Fail に頼る必要があります。
[Test]
public void ThrowsExceptionCorrectly()
{
const string BAD_INPUT = "bad input";
try
{
new MyClass().DoSomething(BAD_INPUT);
Assert.Fail("No exception was thrown");
}
catch (MyCustomException ex)
{
Assert.AreEqual(BAD_INPUT, ex.InputString);
}
}
xUnit.Net メソッド Assert.Throws を使用すると、Assert.Fail メソッドを必要とせずに、これをより簡単に行うことができます。Assert.Fail() メソッドを含めないことで、xUnit.Net は開発者がより明示的な代替手段を見つけて使用し、必要に応じて新しいアサーションの作成をサポートすることを奨励します。
他のヒント
意図的に省略されたのです。これは、Assert.Fail() がない理由に関する Brad Wilson の回答です。
実際、私たちはこれを見逃していませんでした。私はassert.failは松葉杖であり、おそらくアサーションが欠落していることを意味します。テストが構造化されている方法だけで、アサートが別のアサーションを使用できるためです。
私は、単純な値の比較を超えたロジックを通じてテストが失敗する必要があることが検出されたケースを処理するために、常に Assert.Fail() を使用してきました。例として:
try
{
// Some code that should throw ExceptionX
Assert.Fail("ExceptionX should be thrown")
}
catch ( ExceptionX ex )
{
// test passed
}
したがって、フレームワークに Assert.Fail() がないことは、私には間違いのように思えます。Assert クラスにパッチを適用して Fail() メソッドを含め、そのパッチを追加する理由とともにフレームワーク開発者に送信することをお勧めします。
ワークスペースで意図的に失敗するテストを作成し、コミットする前に実装することを自分に思い出させるという習慣については、私にとっては良い習慣のように思えます。
単体テストには MbUnit を使用します。テストを無視するオプションがあり、テスト スイートでは (緑や赤ではなく) オレンジ色で表示されます。おそらく xUnit にも同様の機能があり、メソッドに Assert を組み込む必要さえないということになるでしょうか。煩わしいほど異なる色で表示され、見落としにくくなるからです。
編集:
MbUnit では次のようになります。
[Test]
[Ignore]
public void YourTest()
{ }
これは、仕様により例外をスローするコードのテストを作成するときに使用するパターンです。
[TestMethod]
public void TestForException()
{
Exception _Exception = null;
try
{
//Code that I expect to throw the exception.
MyClass _MyClass = null;
_MyClass.SomeMethod();
//Code that I expect to throw the exception.
}
catch(Exception _ThrownException)
{
_Exception = _ThrownException
}
finally
{
Assert.IsNotNull(_Exception);
//Replace NullReferenceException with expected exception.
Assert.IsInstanceOfType(_Exception, typeof(NullReferenceException));
}
}
私の意見では、これは Assert.Fail() を使用するよりも例外をテストする優れた方法です。その理由は、スローされる例外をテストするだけでなく、例外のタイプもテストするためです。これはMatt Howellsからの回答と似ていると思いますが、私の個人的な意見では、finallyブロックを使用した方がより堅牢です。
明らかに、例外入力文字列などをテストするために他の Assert メソッドを含めることも可能です。私のパターンに関するコメントやご意見をいただければ幸いです。
個人的には、最終的にテストを作成できる限り、このようにテスト スイートを ToDo リストとして使用することに問題はありません。 前に 渡すコードを実装します。
そうは言っても、私自身も以前はこのアプローチを使用していましたが、今ではそうすることで事前に多くのテストを書きすぎるという道をたどることに気づきました。これは、奇妙なことに、テストをまったく書かないという逆の問題のようなものです。私の意見では、デザインについての決定が少し早すぎてしまうのです。
ちなみに、MSTest では、標準の Test テンプレートはサンプルの最後に Assert.Inconclusive を使用します。
私の知る限り、xUnit.NET フレームワークは非常に軽量であることを目的としており、開発者が明示的な失敗条件を使用することを奨励するために、意図的に Fail をカットしました。
当てずっぽう:Assert.Fail を保留するのは、テスト コードを記述するための良い方法は、悪い場合に Assert.Fail につながる巨大なスパゲッティの山であるという考えを阻止することを目的としています。[編集して追加:他の人の回答はこれをほぼ裏付けていますが、引用文が含まれています]
それはあなたがやっていることではないので、xUnit.Net が過保護である可能性があります。
あるいは、それは非常にまれで、非常に非正統的であるため不必要であると彼らは単に考えているのかもしれません。
私は ThisCodeHasNotBeenWrittenYet という関数を実装することを好みます (実際には、入力しやすいようにもっと短いものです)。これ以上に明確に意図を伝えることはできず、正確な検索語を持っています。
それが失敗するか、実装されていない (リンカー エラーを引き起こす) か、コンパイルできないマクロであるかは、現在の設定に合わせて変更できます。たとえば、何かを実行したいとき、 は 終わったら失敗したい。それらをすべて削除しようとしているときに、コンパイル エラーが発生する可能性があります。
良いコードでは、私は通常次のことを行います。
void goodCode() {
// TODO void goodCode()
throw new NotSupportedOperationException("void goodCode()");
}
私が通常行うテストコードでは次のようになります。
@Test
void testSomething() {
// TODO void test Something
Assert.assert("Some descriptive text about what to test")
}
JUnit を使用していて、失敗ではなくエラーが発生したくない場合は、通常次のようにします。
@Test
void testSomething() {
// TODO void test Something
throw new NotSupportedOperationException("Some descriptive text about what to test")
}
気をつけてください Assert.Fail
そして、開発者にばかげた、または壊れたテストを作成させる、その破壊的な影響力。例えば:
[TestMethod]
public void TestWork()
{
try {
Work();
}
catch {
Assert.Fail();
}
}
try-catch は冗長なので、これは愚かです。例外がスローされた場合、テストは失敗します。
また
[TestMethod]
public void TestDivide()
{
try {
Divide(5,0);
Assert.Fail();
} catch { }
}
これは壊れており、Divide 関数の結果が何であれ、テストは常に合格します。繰り返しますが、テストは例外をスローした場合にのみ失敗します。
例外をスローする必要があると言うために Assert.Fail を使用するのはなぜでしょうか?それは不必要です。ExpectedException 属性を使用しないのはなぜでしょうか?
失敗するだけのテストを作成し、そのコードを作成してからテストを作成する場合。これはテスト駆動開発ではありません。
技術的には、テスト駆動開発を正しく使用している場合、Assert.fail() は必要ありません。
Todo リストを使用したり、GTD 方法論を仕事に適用したりすることを考えたことはありますか?
MSテストは Assert.Fail() しかし、それもあります Assert.Inconclusive(). 。Assert.Fail() の最も適切な使用法は、アサーションに入れるのが面倒なインライン ロジックがある場合だと思います。ただし、良い例は思いつきません。ほとんどの場合、テスト フレームワークが Assert.Fail() 以外のものをサポートしている場合は、それを使用します。
(事前の) テストで何を行うべきかを自問する必要があると思います。
まず、実装せずにテスト (セット) を作成します。もしかしたら、雨の日のシナリオもあるかもしれません。
正しいテストであるためには、これらのテストはすべて失敗する必要があります。したがって、次の 2 つのことを達成したいと考えます。1) 実装が正しいことを確認します。2) 単体テストが正しいことを確認します。
さて、アップフロント TDD を行う場合は、すべてのテストと NYI 部分も実行する必要があります。次の場合、合計テスト実行の結果は合格となります。1)実装されたすべてのものが成功する2)すべてのNYIのものが失敗します
結局のところ、実装がないのに単体テストが成功した場合、それは単体テストの省略になります。
最終的には、実装されたコードと実装されていないコードをすべてチェックし、実装されたコードが失敗した場合、または実装されていないコードが成功した場合に送信される、継続的統合テストのメールのようなものを作成したいと考えています。どちらも望ましくない結果です。
[無視] テストを書くだけでは機能しません。どちらも、最初のアサートの失敗を停止するアサートではなく、テスト内の他のテスト行を実行しません。
さて、それではどうやってこれを達成するのでしょうか?より高度なテストの組織化が必要だと思います。そして、これらの目標を達成するには、アサートする他のメカニズムが必要です。
テストを分割して、完全に実行されるが失敗する必要があるテストをいくつか作成する必要があると思います。逆も同様です。
アイデアは、テストを複数のアセンブリに分割し、テストのグループ化を使用することです (mstest の順序付けされたテストが機能する可能性があります)。
それでも、NYI 部門のすべてのテストが失敗しない場合にメールで送信する CI ビルドは、簡単で単純なものではありません。