テストのためのテスト

「なぜ単体テストを書いていないのか」という質問に対する最も一般的な答えの1つは、「誰が私のテストのためにテストを書くのか」という質問です。 私のテストにエラーがないという保証はどこにありますか?」、これは単体テストの本質に対する重大な誤解を示しています。



この記事の目的は、この点を簡潔かつ明確に把握して、これ以上意見の相違がないようにすることです。



ユニットテストは プログラム入力の結果と出力結果を示すいくつかの例のセット。 たとえば、プログラムが斜辺の長さを計算する場合、単体テストでは1行で十分です。

assertEquals(5, hypotenuse(3, 4));
      
      





これらのテスト値を自分で考え出す必要があります。一枚の紙または電卓の列で数えます。

もちろん、このテストで十分かどうかはあなた次第です。 特定のタスクで、関数が非常に小さな数で正しく動作することが非常に重要な場合は、別のテストを追加する必要があります
 assertEquals(0.00005, hypotenuse(0.00003, 0.00004));
      
      



また、負のパラメータまたは他のパラメータの場合の関数の動作を記述することも重要です。 一般的には決定しますが、主なアイデアは、単体テストにいくつかの重要な例があるはずです。




ルール



そのため、単体テストは次の点でプログラムと異なります。

最初の段落は、「私のテストのために誰がテストを書くのか?」という質問に対する答えです。 単体テストはプログラムよりも桁違いに単純なので、単体テストは桁違いに簡単に書く必要がありますが、とにかく非常に単純なので、ほとんど不可能です。



2番目の質問「テストでエラーが発生しないという保証はどこにありますか?」に対する答えは、これです。保証はありません。 しかし、テストで規定された「正しい」解答がプログラムで与えられた解答とは異なる方法で受け取られたという事実により、両方の方法が正しいことをより確信することができます。

UPD: kalantyrは、ユニットテストとコードは相互に接続されているため互いにテストすることを示しています。 つまり、ある意味では、テストのテストはテストされたコードそのものです。





上記のすべてを説明する単体テストの最小限の例をここで示してみましょう。 ある年がうるう年かどうかを判断する関数を作成する必要があるとします。 うるう年とは、100で除算されているが400で除算されていない年を除き、4で除算された年であることを思い出させてください。



 public class LeapYear { public static boolean isLeap(int year) { return year % 4 == 0 && year % 100 != 0 || year % 400 == 0; } } import org.junit.Test; import static junit.framework.Assert.*; public class LeapYearTest { @Test public void yearDividing4IsLeap() throws Exception { assertTrue(isLeap(2008)); assertTrue(isLeap(2012)); assertFalse(isLeap(2011)); } @Test public void exceptYearDividing100WhichIsNotLeap() throws Exception { assertFalse(isLeap(1900)); assertFalse(isLeap(2100)); } @Test public void exceptYearDividing400WhichIsLeap() throws Exception { assertTrue(isLeap(1600)); assertTrue(isLeap(2000)); } }
      
      







ご覧のとおり、テストケース自体の中には、具体的な簡単な例があります。2000、2008、1011。私は自分で頭から発明しました。 また、テストケースの名前は、それらを人間の言語で説明(要約)します。 1対1のテスト方法の名前は、上記の「うるう年」という用語の説明と同じであることに注意してください。 そのため、テストはドキュメントとして読む必要があります。 そして、これは例えばIDEAでの見た目です:

5.97 KB



間違い

ユニットテストの本質を誤解しているため、ユニットテストを書くときに大きな間違いがしばしば発生し、ユニットテストが役に立たず、時間がかかり、ユニットテストが必要かどうかについて無限の議論につながります。



最初のよくある間違いは、何もチェックしないことです。 例:
 @Test public void testLeapYear() throws Exception { int year = 2004; assertEquals(2004, year); }
      
      



この単体テストは、プログラムをテストしないため、意味がありません。 彼がチェックする最大のことは、Javaが正しく動作することですが、これは妄想です。 Oracleにこれを行わせてください。

明らかな不条理にもかかわらず、最初にJUnitを使用するすべての人が開始するのはこのようなテストです。



2番目の典型的な間違いは、プログラム内のテストで同じロジックを繰り返すことです。 例:
 @Test public void testLeapYear() throws Exception { for (int i=0; i<100000; i++) { assertEquals(i % 4 == 0 && i % 100 != 0 || i % 400 == 0, isLeap(i)); } }
      
      



この単体テストは、プログラマーがコード自体と同じ間違いを犯す可能性があるというまさに理由で悪いです。



臭い



単体テストが正しくないことを示すサイン:





オープンソース



有名なオープンソースプロジェクトの単体テストを研究することは常に役立ちます。 このトピックは別の記事に値しますが、最初に頭に浮かんだのはApache VelocitySpring Frameworkです。 最初のUnicodeInputStreamTestCaseプロジェクトのテストは好きではありませんでした。テストメソッドの名前は実際にはプログラムの動作を説明していないためです(たとえば、「testSimpleStream()」)。 しかし、たとえばSpringのテストが好きでした。たとえば、 CollectionUtilsクラスにはCollectionUtilsTests単体テストがあり、 AssertクラスにはAssertTests単体テストがあります。



この記事が、単体テストが役に立たず、時間がかかるなどという永遠の議論を止め、書く方法、書く量、書くものなどに関するさらなる議論の出発点として役立つことを願っています。 そして、誰かがハブから脱出し 、「 Test Driven Development(Kent Beck) 」、「 xUnit Test Patterns(Gerard Meszaros) 」、私のお気に入りの「 Clean Code(Robert C. Martin) ) 」。



テストするかしないか-それは質問ではありません...






All Articles