JUnit 4.8には、「カテゴリ」という優れた新機能が含まれており、特定の種類のテストをグループ化できます。これは非常に便利です。低速テストと高速テストのテストを別々に実行する。 JUnit 4.8 release notes で言及されているものは知っていますが、特定のカテゴリで注釈付けされたすべてのテストを実際に実行する方法を知りたいです。
JUnit 4.8リリースノートにはスイート定義の例が示されており、SuiteClassesアノテーションは次のように特定のカテゴリからテストを選択して実行します。
@RunWith(Categories.class)
@IncludeCategory(SlowTests.class)
@SuiteClasses( { A.class, B.class }) // Note that Categories is a kind of Suite
public class SlowTestSuite {
// Will run A.b and B.c, but not A.a
}
SlowTestsカテゴリのすべてのテストを実行する方法を知っている人はいますか? SuiteClassesアノテーションが必要なようです...
私が望むものを達成するための1つの可能な方法を見つけましたが、これはJUnitの一部ではないClassPathSuiteライブラリに依存しているため、これが最善の解決策であるとは考えていません。
次のような低速テスト用のテストスイートを定義します。
@RunWith(Categories.class)
@Categories.IncludeCategory(SlowTests.class)
@Suite.SuiteClasses( { AllTests.class })
public class SlowTestSuite {
}
AllTestsクラスは次のように定義されます:
@RunWith(ClasspathSuite.class)
public class AllTests {
}
ここでは ClassPathSuite プロジェクトのClassPathSuiteクラスを使用する必要がありました。それはテストですべてのクラスを見つけます。
TestNGとJUnitのグループ(またはJUnitが呼び出すようなカテゴリ)についての主な違いは次のとおりです。
TestUnitは文字列ですが、JUnitは入力されます(注釈)。この選択をしたのは、テストの実行時に正規表現を使用できるようにしたかったためです。たとえば、「グループ「データベース*」に属するすべてのテストを実行します。また、新しいアノテーションを作成する必要があるたびに、 categoryは迷惑ですが、IDEはこのカテゴリが使用されている場所をすぐに教えてくれるという利点があります(TestNGはレポートでこれを示しています)。
TestNGは、静的モデル(テストのコード)とランタイムモデル(テストが実行される)を明確に分離します。最初にグループ「フロントエンド」を実行し、次に「サーブレット」を実行する場合は、何も再コンパイルせずにこれを実行できます。 JUnitは注釈でグループを定義し、これらのカテゴリをランナーへのパラメータとして指定する必要があるため、通常、別のカテゴリセットを実行するたびにコードを再コンパイルする必要があり、これは私の意見では目的を無効にします。
Kaitsuのソリューションの欠点の1つは、プロジェクト内のすべてのテストを実行するときに Eclipse がテストを2回実行し、SlowTestsを3回実行することです。これは、Eclipseがすべてのテスト、AllTestsスイート、SlowTestSuiteの順に実行するためです。
特定のシステムプロパティが設定されていない限り、スイートをスキップするために、Kaitsuソリューションのテストランナーのサブクラスを作成するソリューションがあります。恥ずべきハックですが、私がこれまでに思いついたすべてのことです。
@RunWith(DevFilterClasspathSuite.class)
public class AllTests {}
。
@RunWith(DevFilterCategories.class)
@ExcludeCategory(SlowTest.class)
@SuiteClasses(AllTests.class)
public class FastTestSuite
{
}
。
public class DevFilterCategories extends Suite
{
private static final Logger logger = Logger
.getLogger(DevFilterCategories.class.getName());
public DevFilterCategories(Class<?> suiteClass, RunnerBuilder builder) throws InitializationError {
super(suiteClass, builder);
try {
filter(new CategoryFilter(getIncludedCategory(suiteClass),
getExcludedCategory(suiteClass)));
filter(new DevFilter());
} catch (NoTestsRemainException e) {
logger.info("skipped all tests");
}
assertNoCategorizedDescendentsOfUncategorizeableParents(getDescription());
}
private Class<?> getIncludedCategory(Class<?> klass) {
IncludeCategory annotation= klass.getAnnotation(IncludeCategory.class);
return annotation == null ? null : annotation.value();
}
private Class<?> getExcludedCategory(Class<?> klass) {
ExcludeCategory annotation= klass.getAnnotation(ExcludeCategory.class);
return annotation == null ? null : annotation.value();
}
private void assertNoCategorizedDescendentsOfUncategorizeableParents(Description description) throws InitializationError {
if (!canHaveCategorizedChildren(description))
assertNoDescendantsHaveCategoryAnnotations(description);
for (Description each : description.getChildren())
assertNoCategorizedDescendentsOfUncategorizeableParents(each);
}
private void assertNoDescendantsHaveCategoryAnnotations(Description description) throws InitializationError {
for (Description each : description.getChildren()) {
if (each.getAnnotation(Category.class) != null)
throw new InitializationError("Category annotations on Parameterized classes are not supported on individual methods.");
assertNoDescendantsHaveCategoryAnnotations(each);
}
}
// If children have names like [0], our current magical category code can't determine their
// parentage.
private static boolean canHaveCategorizedChildren(Description description) {
for (Description each : description.getChildren())
if (each.getTestClass() == null)
return false;
return true;
}
}
。
public class DevFilterClasspathSuite extends ClasspathSuite
{
private static final Logger logger = Logger
.getLogger(DevFilterClasspathSuite.class.getName());
public DevFilterClasspathSuite(Class<?> suiteClass, RunnerBuilder builder)
throws InitializationError {
super(suiteClass, builder);
try
{
filter(new DevFilter());
} catch (NoTestsRemainException e)
{
logger.info("skipped all tests");
}
}
}
。
public class DevFilter extends Filter
{
private static final String RUN_DEV_UNIT_TESTS = "run.dev.unit.tests";
@Override
public boolean shouldRun(Description description)
{
return Boolean.getBoolean(RUN_DEV_UNIT_TESTS);
}
@Override
public String describe()
{
return "filter if "+RUN_DEV_UNIT_TESTS+" system property not present";
}
}
そのため、FastTestSuiteランチャーで、-= Drun.dev.unit.tests = trueをVM引数に追加するだけです(このソリューションは、低速のテストスイートではなく高速のテストスイートを参照することに注意してください)。
すべてのテストを@Suite.SuiteClasses
アノテーションで明示的に指定せずに分類されたテストを実行するには、Suiteの独自の実装を提供できます。たとえば、org.junit.runners.ParentRunner
は拡張できます。 @Suite.SuiteClasses
で提供されるクラスの配列を使用する代わりに、新しい実装はクラスパスで分類されたテストの検索を実行する必要があります。
そのようなアプローチの例として this project を参照してください。使用法:
@Categories(categoryClasses = {IntegrationTest.class, SlowTest.class})
@BasePackage(name = "some.package")
@RunWith(CategorizedSuite.class)
public class CategorizedSuiteWithSpecifiedPackage {
}
正確にはあなたの問題は何なのかわかりません。
すべてのテストをスイート(またはスイートのヒラキラ)に追加するだけです。次に、カテゴリランナーとInclude/ExcludeCategoryアノテーションを使用して、実行するカテゴリを指定します。
すべてのテストを含む1つのスイートと、最初のスイートを参照する2、3のスイートを用意して、必要なカテゴリの異なるセットを指定することをお勧めします。
あなたの問題に対する直接的な答えではありませんが、一般的なアプローチは改善されるかもしれません...
テストが遅いのはなぜですか?セットアップが長く続く場合(データベース、I/Oなど)、テストのテストが多すぎるのかもしれません。もしそうなら、私は実際のユニットテストを「長時間」のテストから分離するでしょう。これはしばしば統合テストです。
私のセットアップでは、ステージング環境があり、ユニットテストが頻繁に実行され、統合テストが絶えず実行されますが、まれです(たとえば、バージョン管理での各コミット後)。ユニットテストのグループ化は、これまで一緒に行ったことがありません。それらはすべて疎結合である必要があるためです。私は統合テストのセットアップでテストケースのグループ化とリレーションシップのみを使用します(ただしTestNGを使用)。
しかし、JUnit 4.8にはいくつかのグループ化機能が導入されたことを知っておくと良いでしょう。