意図
私は次のものを探しています:
- 堅実な単体テスト方法論
- 私のアプローチには何が欠けていますか?
- 私は何を間違っていますか?
- 私は何をしているのですか?
- できるだけ多くを自動的に行う方法
現在の環境
- IDEとしてのEclipse
- Eclipse に統合されたテスト フレームワークとしてのJUnit
- アサーションの読みやすさを向上させるための「マッチャー」ライブラリとしてのHamcrest
- 前提条件検証のためのGoogle Guava
現在のアプローチ
構造
- テストするクラスごとに 1 つのテスト クラス
- ネストされた静的クラスにグループ化されたメソッド テスト
- テストされた動作と期待される結果を指定するためのテスト メソッドの命名
- メソッド名ではなく、 Java Annotationによって指定された予期される例外
方法論
null
値に注意- 空のList<E>に注意してください
- 空の文字列に注意してください
- 空の配列に注意してください
- コードによって変更されたオブジェクト状態の不変条件に注意してください (事後条件)
- メソッドは、文書化されたパラメーターの型を受け入れます
- 境界チェック (例: Integer.MAX_VALUEなど)
- 特定の型による不変性の文書化 (例: Google Guava ImmutableList<E> )
- ...これのリストはありますか?あると便利なテスト リストの例:
- データベース プロジェクトでチェックする項目 (例: CRUD、接続性、ロギングなど)
- マルチスレッドコードで確認すること
- EJB の確認事項
- ... ?
サンプルコード
これは、いくつかのテクニックを示すための不自然な例です。
MyPath.java
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Arrays;
import com.google.common.collect.ImmutableList;
public class MyPath {
public static final MyPath ROOT = MyPath.ofComponents("ROOT");
public static final String SEPARATOR = "/";
public static MyPath ofComponents(String... components) {
checkNotNull(components);
checkArgument(components.length > 0);
checkArgument(!Arrays.asList(components).contains(""));
return new MyPath(components);
}
private final ImmutableList<String> components;
private MyPath(String[] components) {
this.components = ImmutableList.copyOf(components);
}
public ImmutableList<String> getComponents() {
return components;
}
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
for (String pathComponent : components) {
stringBuilder.append("/" + pathComponent);
}
return stringBuilder.toString();
}
}
MyPathTests.java
import static org.hamcrest.Matchers.is;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
import static org.hamcrest.collection.IsEmptyCollection.empty;
import static org.hamcrest.collection.IsIterableContainingInOrder.contains;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.hamcrest.core.IsNot.not;
import static org.hamcrest.core.IsNull.notNullValue;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import org.junit.experimental.runners.Enclosed;
import org.junit.runner.RunWith;
import com.google.common.base.Joiner;
@RunWith(Enclosed.class)
public class MyPathTests {
public static class GetComponents {
@Test
public void componentsCorrespondToFactoryArguments() {
String[] components = { "Test1", "Test2", "Test3" };
MyPath myPath = MyPath.ofComponents(components);
assertThat(myPath.getComponents(), contains(components));
}
}
public static class OfComponents {
@Test
public void acceptsArrayOfComponents() {
MyPath.ofComponents("Test1", "Test2", "Test3");
}
@Test
public void acceptsSingleComponent() {
MyPath.ofComponents("Test1");
}
@Test(expected = IllegalArgumentException.class)
public void emptyStringVarArgsThrows() {
MyPath.ofComponents(new String[] { });
}
@Test(expected = NullPointerException.class)
public void nullStringVarArgsThrows() {
MyPath.ofComponents((String[]) null);
}
@Test(expected = IllegalArgumentException.class)
public void rejectsInterspersedEmptyComponents() {
MyPath.ofComponents("Test1", "", "Test2");
}
@Test(expected = IllegalArgumentException.class)
public void rejectsSingleEmptyComponent() {
MyPath.ofComponents("");
}
@Test
public void returnsNotNullValue() {
assertThat(MyPath.ofComponents("Test"), is(notNullValue()));
}
}
public static class Root {
@Test
public void hasComponents() {
assertThat(MyPath.ROOT.getComponents(), is(not(empty())));
}
@Test
public void hasExactlyOneComponent() {
assertThat(MyPath.ROOT.getComponents(), hasSize(1));
}
@Test
public void hasExactlyOneInboxComponent() {
assertThat(MyPath.ROOT.getComponents(), contains("ROOT"));
}
@Test
public void isNotNull() {
assertThat(MyPath.ROOT, is(notNullValue()));
}
@Test
public void toStringIsSlashSeparatedAbsolutePathToInbox() {
assertThat(MyPath.ROOT.toString(), is(equalTo("/ROOT")));
}
}
public static class ToString {
@Test
public void toStringIsSlashSeparatedPathOfComponents() {
String[] components = { "Test1", "Test2", "Test3" };
String expectedPath =
MyPath.SEPARATOR + Joiner.on(MyPath.SEPARATOR).join(components);
assertThat(MyPath.ofComponents(components).toString(),
is(equalTo(expectedPath)));
}
}
@Test
public void testPathCreationFromComponents() {
String[] pathComponentArguments = new String[] { "One", "Two", "Three" };
MyPath myPath = MyPath.ofComponents(pathComponentArguments);
assertThat(myPath.getComponents(), contains(pathComponentArguments));
}
}
明確に表現された質問
単体テストを作成するために使用するテクニックのリストはありますか? 上記の非常に単純化されたリストよりもはるかに高度なもの (null のチェック、境界のチェック、予想される例外のチェックなど) は、購入する本やアクセスする URL で利用できるでしょうか?
特定のタイプのパラメーターを取るメソッドを作成したら、 Eclipseプラグインを入手してテスト用のスタブを生成できますか? おそらく、Javaアノテーションを使用してメソッドに関するメタデータを指定し、ツールに関連するチェックを実体化させるのでしょうか? (例: @MustBeLowerCase、@ShouldBeOfSize(n=3)、...)
これらの「QA トリック」をすべて覚えたり、適用したりする必要があるのは、退屈でロボットのようだと思います。その上。確かに、Hamcrestライブラリはテストの種類を特殊化するという一般的な方向に進んでいますが (たとえば、RegEx を使用するStringオブジェクト、 Fileオブジェクトなど)、明らかにテスト スタブを自動生成せず、コードとそのプロパティを反映して準備しません。私のためのハーネス。
これを改善するのを手伝ってください。
PS
静的ファクトリメソッドで提供されたパスステップのリストからパスを作成するという概念の愚かなラッパーであるコードを提示しているだけだとは言わないでください。これは完全に作成された例ですが、「いくつかの" 引数の検証のケース...もっと長い例を含めると、誰がこの投稿を本当に読むでしょうか?