如何在JUnit4中按特定顺序运行测试方法?

472

我希望按特定顺序执行被标记为 @Test 的测试方法。

例如:

public class MyTest {
    @Test public void test1(){}
    @Test public void test2(){}
}

我希望每次运行MyTest时,都能确保在运行test2()之前先运行test1(),但我找不到类似于@Test(order=xx)的注解。

我认为这是JUnit的一个重要功能,如果JUnit的作者不想要有序功能,那么为什么呢?


103
不应编写需要按特定顺序执行的测试,这是非常不好的实践。每个测试都应该能够独立运行。 - Apfelsaft
6
在Java 7之前,几乎所有的Java虚拟机都会按照一定顺序返回方法。但是并没有得到保证。而Java 7及以后的版本的虚拟机则可以以非确定性的顺序返回方法。 - Matthew Farwell
19
绕过问题。从测试用例中删除@Test,将它们转换为私有函数,然后创建一个单一的测试用例,并按顺序调用私有函数。 - Simon Guo
17
如果从测试用例中移除@Test注解,将会影响JUnit报告。顺便提一句,强制执行特定的顺序是单元测试的坏习惯,但不一定是集成测试的坏习惯。最好的选择(虽然不完美)是使用@FixMethodOrder(MethodSorters.NAME_ASCENDING)为类进行注释,并保留所有测试方法的@Test注解,并根据期望的执行顺序按字母顺序重命名它们,例如t1_firstTest()t2_secondTest()等。 - MisterStrickland
9
谈论单元测试需要独立的问题很容易,但按特定顺序运行测试仍有非常好的理由。在我的情况下,我为每个可能的输入参数值运行三个独立的测试。对于每个可能的值,我想比较这三个测试,如果它们在输出中被分组在一起,这样做会更容易。这还有助于我识别测试失败中的模式。因此,感谢那些实际回答了问题的人。 - MiguelMunoz
显示剩余9条评论
23个回答

254
我认为这对JUnit来说是非常重要的功能,如果JUnit的作者不想要这个排序功能,那么为什么呢?
据我所知,使用JUnit没有明确的方法可以做到这一点,因为JUnit假设所有的测试都可以按任意顺序执行。参考FAQ: 如何使用测试fixture? (…)测试方法的调用顺序不能保证,因此 testOneItemCollection() 方法可能在 testEmptyCollection() 方法之前执行。(…)
为什么会这样呢?我相信这是JUnit作者不愿促进的一种实践,即测试应该是独立的,它们不应该存在耦合性,违反这一点将会使维护变得更加困难,将会破坏单独运行测试的能力(显然),等等。
话虽如此,如果您真的想朝这个方向发展,请考虑使用 TestNG,因为它支持原生地以任意顺序运行测试方法(以及指定方法依赖于一组方法等功能)。Cedric Beust 在testng中的测试执行顺序中解释了如何做到这一点。

15
你要么有两个独立的测试,要么只有一个测试,应该相应地进行编码。 - Jon Freedman
193
我可以理解在单元测试中不强制执行顺序,但是当使用JUnit编写集成测试时,能够指定测试运行的顺序会更好。例如:首先运行登录测试。 - Brian DiCasa
14
@BrianD. 登录可能是一个“固定项”,而不是必须在所有其他测试之前运行的测试。我可能会编写一个BeforeClass,先登录,然后编写测试以任意顺序执行。 - marcospereira
56
“测试应该是独立的 => 测试应该是无序的”这个暗示是不正确的。考虑对学生作业进行自动评分。我希望先测试他们的解决方案是否适用于小输入,然后再测试更大的输入。当解决方案在较小的输入上失败时(例如时间/内存限制),为什么还要运行更大的输入的测试呢? - mirelon
4
请注意,JUnit不会按照类中定义的顺序运行测试方法。至少在JUnit 4.10中,我曾经看到它们以不同的顺序运行过。 - studgeek
显示剩余7条评论

133

如果您想要移除当前的Junit实例,并下载JUnit 4.11或更高版本到构建路径,以下代码将按照测试方法名称的字母顺序执行它们:

import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;

@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class SampleTest {

    @Test
    public void testAcreate() {
        System.out.println("first");
    }
    @Test
    public void testBupdate() {
        System.out.println("second");
    }
    @Test
    public void testCdelete() {
        System.out.println("third");
    }
}

12
我们实际上尝试过一个类似的方法test_001_login(),但虽然它大多数情况下可以保持顺序,但并不能得到保证-每次测试运行中都有几个实例会在007之后运行004、005和006。这会让你感到非常困惑,并且会去StackOverflow找答案。 - Max P Magee
2
在我的测试中:testAcase - 工作正常,test_A_case / testA_case - 没有! - Rodislav Moldovan
8
我尝试了这个注释参数"MethodSorters.JVM",例如"@FixMethodOrder(MethodSorters.JVM)"。根据 API 文档,JVM 表示按照 JVM 返回的顺序保留测试方法。对于我的 CRUD 操作来说,这个方式很有效,可以按照测试方法编写的顺序运行。+1 - Edvinauskas
2
这个注解确实是一个答案,但它有一个警告,即在Junit 4.12中没有定义@Inherited,因此在我的AbstractTestCase父类上变得无效。 - AbVog
我有 jUnit 4.12,这个 hack 不起作用。 - Igor Kanshyn

55

迁移到TestNG似乎是最好的方式,但我在这里对于jUnit没有找到明确的解决方案。以下是我发现的jUnit中最易读的解决方案/格式:

Migration to TestNG 似乎 是 最好 的 方式 , 但 我 在 这里 对于 jUnit 没有 找到 明确 的 解决方案 。 以下 是 我 发现 的 jUnit 中 最易 读的解决方案 / 格式 :

@FixMethodOrder( MethodSorters.NAME_ASCENDING ) // force name ordering
public class SampleTest {
    @Test
    void stage1_prepareAndTest(){};

    @Test
    void stage2_checkSomething(){};

    @Test
    void stage2_checkSomethingElse(){};

    @Test
    void stage3_thisDependsOnStage2(){};

    @Test
    void callTimeDoesntMatter(){}
}

这样可以确保在第一阶段方法之后、第三阶段方法之前调用第二阶段方法。

注:我认为这种方法比 jUnit 5.5 的 @Order 注释更好,因为它为读者提供了更好的符号表示法。


6
这个方法很好,但需要说明的是,如果您有超过10个测试,除非您添加“0”前缀,否则它可能无法正常工作,例如void stage01_prepareAndTest(){ } - EliuX

52

如果顺序很重要,你应该自己制定顺序。

@Test public void test1() { ... }
@Test public void test2() { test1(); ... }

特别地,如果必要的话,您应列出一些或全部可能的订单排列进行测试。

例如,

void test1(); 
void test2(); 
void test3(); 


@Test
public void testOrder1() { test1(); test3(); }

@Test(expected = Exception.class)
public void testOrder2() { test2(); test3(); test1(); }

@Test(expected = NullPointerException.class)
public void testOrder3() { test3(); test1(); test2(); }

或者,对所有排列进行全面测试:

@Test
public void testAllOrders() {
    for (Object[] sample: permute(1, 2, 3)) {
        for (Object index: sample) {
            switch (((Integer) index).intValue()) {
                case 1: test1(); break; 
                case 2: test2(); break; 
                case 3: test3(); break; 
            }
        }
    }
}

这里的permute()是一个简单的函数,它将所有可能的排列迭代到一个数组的集合中。


7
在第一个代码块中,test2 再次运行 test1。Junit 可能会在 test1 之前运行 test2。这很可能不是你想要的结果,也不能回答问题。 - toolforger
这将在测试之间共享状态,并且@Before仅会被调用一次,这对于更复杂的测试(如在Android上测试Activities)是不希望的。 - Mauricio Rodriguez

28
自JUnit 5.5以来,允许在类上使用@TestMethodOrder(OrderAnnotation.class)和在测试方法上使用@Order(1)进行排序。
以前版本的JUnit允许使用类注释对测试方法进行排序:
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@FixMethodOrder(MethodSorters.JVM)
@FixMethodOrder(MethodSorters.DEFAULT)

默认情况下,测试方法按字母顺序运行。因此,要设置特定的方法顺序,您可以将它们命名为:

a_TestWorkUnit_WithCertainState_ShouldDoSomething b_TestWorkUnit_WithCertainState_ShouldDoSomething c_TestWorkUnit_WithCertainState_ShouldDoSomething

或者

_1_TestWorkUnit_WithCertainState_ShouldDoSomething _2_TestWorkUnit_WithCertainState_ShouldDoSomething _3_TestWorkUnit_WithCertainState_ShouldDoSomething

您可以在这里找到示例。


23

JUnit 5更新(及我的观点)

我认为这是JUnit非常重要的功能,如果JUnit的作者不想要有序特性,那么为什么呢?

默认情况下,单元测试库不会按照源文件中出现的顺序来执行测试。

JUnit 5和JUnit 4都是以这种方式工作的。为什么?因为如果顺序很重要,那么一些测试就会彼此耦合,这对于单元测试来说是不可取的。
因此,JUnit 5引入的@Nested特性遵循同样的默认方法。

但是对于集成测试,测试方法的顺序可能很重要,因为一个测试方法可能会以另一个测试方法所期望的方式改变应用程序的状态。
例如,当您编写电子商务网站的结帐处理集成测试时,要执行的第一个测试方法是注册客户端,第二个是向购物篮中添加商品,最后一个是进行结账。如果测试运行程序不尊重该顺序,则测试场景就存在缺陷并且将失败。
因此,在JUnit 5(从5.4版本开始),您仍然可以通过对测试类进行注释@TestMethodOrder(OrderAnnotation.class)并通过为需要有序的方法指定顺序@Order(numericOrderValue)来设置执行顺序。

例如:

@TestMethodOrder(OrderAnnotation.class) 
class FooTest {

    @Order(3)
    @Test
    void checkoutOrder() {
        System.out.println("checkoutOrder");
    }

    @Order(2)
    @Test
    void addItemsInBasket() {
        System.out.println("addItemsInBasket");
    }

    @Order(1)
    @Test
    void createUserAndLogin() {
        System.out.println("createUserAndLogin");
    }
}

输出:

createUserAndLogin(创建用户并登录)

addItemsInBasket(添加商品到购物篮)

checkoutOrder(结算订单)

顺便说一句,指定@TestMethodOrder(OrderAnnotation.class)看起来不是必需的(至少在我测试的5.4.0版本中)。

附注
关于问题:使用JUnit 5编写集成测试是否是最佳选择? 我认为这不应该是首要考虑的工具(Cucumber等可能经常为集成测试带来更具体的价值和特性),但在某些集成测试情况下,JUnit框架已经足够了。因此,这是一个好消息,这个功能存在。


@Order不像预期的那样工作。它以随机方式或按字母数字顺序执行方法。 - Mamta Garg

21

这是我在使用Junit时遇到的主要问题之一,我想出了以下解决方案,对我来说效果很好:

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;

public class OrderedRunner extends BlockJUnit4ClassRunner {

    public OrderedRunner(Class<?> clazz) throws InitializationError {
        super(clazz);
    }

    @Override
    protected List<FrameworkMethod> computeTestMethods() {
        List<FrameworkMethod> list = super.computeTestMethods();
        List<FrameworkMethod> copy = new ArrayList<FrameworkMethod>(list);
        Collections.sort(copy, new Comparator<FrameworkMethod>() {
        
            @Override
            public int compare(FrameworkMethod f1, FrameworkMethod f2) {
                Order o1 = f1.getAnnotation(Order.class);
                Order o2 = f2.getAnnotation(Order.class);

                if (o1 == null || o2 == null) {
                    return -1;
                }

                return o1.order() - o2.order();
            }
        });
        return copy;
    }
}

还要创建一个如下所示的接口:

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD})
public @interface Order {
    public int order();
}

现在假设您有一个A类,其中编写了以下几个测试用例:

@RunWith(OrderRunner.class)
class A {
    @Test
    @Order(order = 1)
    void method() {
        //do something
    }
}

因此,执行将从名为“method()”的方法开始。

谢谢!


8

2
似乎#386已被合并到4.11中。 - Jesse Glick
1
@SortMethodsWith 已更名为 @FixMethodOrder 并且 在 4.11 版本中发布 - M. Justin

7
看看JUnit报告。JUnit已经按照包进行了组织。每个包都有(或可以有)TestSuite类,每个TestSuite类又运行多个TestCases。每个TestCase可以有多个形式为public void test*()的测试方法,而每个测试方法实际上都会成为所属的TestCase类的实例。每个测试方法(TestCase实例)都有一个名称和通过/不通过的评判标准。
我的管理层需要的是单独的TestStep项的概念,它们各自报告其自己的通过/不通过的评判标准。任何测试步骤的失败都不能阻止后续测试步骤的执行。
过去,在我这样的位置上的测试开发人员将TestCase类组织成与测试产品的部分相对应的包,并为每个测试创建一个TestCase类,将每个测试方法作为测试的单独“步骤”,并在JUnit输出中为每个测试步骤提供其自己的通过/不通过标准。每个TestCase都是一个独立的“测试”,但是TestCase内的单独方法或测试“步骤”必须以特定的顺序出现。
TestCase方法是TestCase的步骤,测试设计师得到了每个测试步骤的独立通过/不通过标准。现在测试步骤是混乱的,测试(当然)会失败。
例如:
Class testStateChanges extends TestCase

public void testCreateObjectPlacesTheObjectInStateA()
public void testTransitionToStateBAndValidateStateB()
public void testTransitionToStateCAndValidateStateC()
public void testTryToDeleteObjectinStateCAndValidateObjectStillExists()
public void testTransitionToStateAAndValidateStateA()
public void testDeleteObjectInStateAAndObjectDoesNotExist()
public void cleanupIfAnythingWentWrong()

每个测试方法都有自己独立的通过/失败标准并进行报告。为了排序而将其合并成“一个大测试方法”会丢失JUnit摘要报告中每个“步骤”的通过/失败标准细节,这让我的管理者感到不满。他们目前正在要求另一种选择。
有人能解释一下,如果JUnit的测试方法顺序被打乱,如何支持每个顺序测试步骤的单独通过/失败标准,就像上面的例子和我们的管理层所需的那样?
无论文档如何,我认为这是JUnit框架的一个严重退化,使得很多测试开发人员的工作变得困难。

6

我不确定我是否同意。如果我想测试“文件上传”,然后测试“由文件上传插入的数据”,为什么我不希望它们彼此独立?我认为完全合理,可以分别运行它们,而不是将两者放在一个Goliath测试用例中。


人们经常会混淆单元测试和集成测试。他们试图将单元测试的最佳实践应用于集成测试,认为“他们遵循了最佳实践”。不幸的是,这些人会制造很多噪音! - RamPrakash

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接