2010-02-01 77 views
66

中運行屬於某個類別的所有測試JUnit 4.8包含一個很好的新功能,稱爲「類別」,允許您將某些類型的測試分組在一起。這是非常有用的,例如對慢速和快速測試進行單獨的測試運行。我知道JUnit 4.8 release notes中提到的東西,但想知道如何實際運行所有使用特定類別註釋的測試。如何在JUnit 4

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

嗨。我有一個相關的問題。隨時chime:http://stackoverflow.com/questions/15776718/using-junit-categories-vs-simply-organizing-logical-test-categories-in-separate – amphibient

回答

59

我發現了一種可能的方式來實現我想要的,但我不認爲這是最好的解決方案,因爲它依賴於不屬於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項目。它會找到所有的測試類。

+4

這實際上是一個相當合理的溶劑。感謝您對自己的問題的微笑,因爲它是一個非常好的問題:-) –

+0

對於任何人想知道如何使用Ant自動運行一個測試類別(使用這個確切的設置),[這個問題](http://stackoverflow.com/questions/6226026/how-to-run-all-junit-tests-in-a-class-suite-with-ant)可能會有用。 – Jonik

+3

作爲對我的問題http://stackoverflow.com/q/2698174/59470的恭維和詳細的解釋,我添加了一個博客條目:http://novyden.blogspot.com/2011/06/using-junit-4-categories -取代。html – topchef

1

我不確定,你的問題到底是什麼。

只需將所有測試添加到套件(或多套套件)即可。然後使用Categories Runner和Include/ExcludeCategory註釋來指定要運行的類別。

一個好主意可能是擁有一個包含所有測試的套件,以及一些指向第一個套件的獨立套件,指定您需要的不同類別集合。

+19

我的問題是,我有成千上萬的測試和我不想手動將它們添加到任何套件。我只想運行某個類別的測試。對於JUnit而言,找出哪些測試具有某些註釋並不難,因爲無論如何,找到測試方法時都會這樣做。 – Kaitsu

1

沒有直接回答你的問題,但也許一般的方法可以改善......

爲什麼你的測試慢?也許設置持續很久(數據庫,I/O等),也許測試測試太多?如果是這樣的話,我會從「長期運行」的單元測試中分離出真正的單元測試,這通常是集成測試。

在我的設置中,我有分段env,其中單元測試經常運行,集成測試不斷,但更爲罕見(例如每次在版本控制中提交後)。我從來沒有與單元測試分組合作過,因爲它們應該鬆散耦合在一起。我只能在集成測試設置中使用測試用例的分組和關係(但使用TestNG)。

但很高興知道JUnit 4.8引入了一些分組功能。

+1

感謝Manuel的評論!我並不需要單獨進行單元測試,但是我也使用JUnit進行集成測試,並希望將它們與單元測試分開。我也看過TestNG,它似乎使測試(而不僅僅是單元測試)比JUnit更好。它也有更好的文檔和一本好書。 – Kaitsu

7

以下是一些TestNG的和JUnit之間的主要區別,當涉及到組(或類,JUnit的稱他們):

  • JUnit的是有類型的(註釋),而TestNG的是字符串。我做了這個選擇是因爲我想在運行測試時能夠使用正則表達式,例如「運行屬於該組的所有測試」database *「。此外,無論何時需要創建新類別,都必須創建一個新註釋,儘管它具有IDE將立即告訴您使用此類別的好處的好處(TestNG在其報告中向您顯示了這一點)。

  • TestNG非常清楚地將您的靜態模型(您的測試代碼)與運行時模型(哪些測試運行)分開。如果你想先運行組「前端」,然後再運行「servlets」,你可以做到這一點,而無需重新編譯任何東西。由於JUnit在註釋中定義了組,並且您需要將這些類別指定爲運行器的參數,所以當您想運行不同的類別時,通常必須重新編譯代碼,這違背了我的看法。

+0

我們以與JUnit非常類似的方式將我們自己的類別支持構建到JUnit測試中,主要區別在於不是通過@ Categories.IncludeCategory註釋,而是通過系統屬性對其進行配置。爲什麼JUnit對我們來說太難了,這是任何人的猜測。 – Trejkaz

2

要沒有@Suite.SuiteClasses註釋explicily指定所有的人,你可以提供自己的套件的運行分類測試。 例如可以擴展org.junit.runners.ParentRunner。新的實現不應該使用@Suite.SuiteClasses提供的類的數組,而應該在類路徑中搜索分類的測試。

請參閱this project作爲這種方法的一個例子。 用法:

@Categories(categoryClasses = {IntegrationTest.class, SlowTest.class}) 
@BasePackage(name = "some.package") 
@RunWith(CategorizedSuite.class) 
public class CategorizedSuiteWithSpecifiedPackage { 

} 
5

一個缺點佳逸的解決方案是,將Eclipse運行測試兩次,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參數。 (請注意,這個解決方案引用了一個快速測試套件,而不是一個慢速測試套件。)