【问题标题】:How to order @Before methods如何订购@Before 方法
【发布时间】:2026-01-22 05:30:02
【问题描述】:

我有一个特性,它添加了几个测试和之前的块。具体实例的 @Before 块在特征中的块之前运行。糟糕,这意味着我不能截断数据库表然后插入固定装置:

trait DatabaseTest {
  @Before
  def truncate() {
    // "TRUNCATE %s".format(tableName)
  }

  def tableName
}

class PersonasTest extends DatabaseTest {
  @Before
  def addPersona() {
    // "INSERT INTO %s VALUES (...)".format(tableName)
  }


  @Test
  def testRejectsInsertWhenAlreadyInTable() {
    // "INSERT INTO %s VALUES (...)".format(tableName)
  }

  def tableName = "personas"
}

testRejectsInsertWhenAlreadyInTable 总是会成功,因为执行顺序是:

  • addPersona
  • truncate
  • testRejectsInsertWhenAlreadyInTable

在不对子类施加太多约束的情况下,对 @Before 块进行排序的正确方法是什么?我总是可以在 trait 中声明 truncate,然后在子类中有一个 @Before 方法,但是我必须记住让我的所有子类都调用该 truncate 方法。

在 Scala 2.9.0.1 上使用 JUnit 4.10。

【问题讨论】:

    标签: scala junit junit4


    【解决方案1】:

    这样做的正确方法是使用@Rule,扩展@ExternalResource 用于前后类型行为(在Java 语法中):

    @Rule
    public ExternalResource resource= new ExternalResource() {
            @Override
            protected void before() throws Throwable {
                    myServer.connect();
            };
    
            @Override
            protected void after() {
                    myServer.disconnect();
            };
    };
    

    您可以使用 @RuleChain(在 4.10 中引入)将多个 @Rules 按顺序链接和排序,同样在 java 语法中:

    @Rule
    public TestRule chain= RuleChain
                           .outerRule(new LoggingRule("outer rule")
                           .around(new LoggingRule("middle rule")
                           .around(new LoggingRule("inner rule");
    

    有一个警告。您不能在 scala 中指定公共字段(公共字段由访问器方法包装,并且字段本身变为私有)。 JUnit 检查 @Rule 是否适用于公共字段。更改 JUnit 代码的修复程序,以便您可以将 @Rule 应用于方法和字段。

    这已经(由我)修复,并已合并到 master 中,但不幸的是,它尚未发布:它将成为 4.11 的一部分。所以你有两个选择:使用 4.11-SNAPSHOT,或者下载 4.10 版本并应用patch for @Rule

    scala 代码可能类似于:

    trait DatabaseTest {
        def truncate(): TestRule = {
            new ExternalResource() {
                override def before() = {
                    // "TRUNCATE %s".format(tableName)
                }
            }
        }
    
        def extra(): TestRule = {
            // return a no-op rule
        }
    
        @Rule def testRule() = new RuleChain(truncate(), extra())
        def tableName
    }
    
    class PersonasTest extends DatabaseTest {
      def extra(): TestRule {
            new ExternalResource() {
                override def before() = {
                    // "INSERT INTO %s VALUES (...)".format(tableName)
                }
            }
      }
    
      @Test
      def testRejectsInsertWhenAlreadyInTable() {
        // "INSERT INTO %s VALUES (...)".format(tableName)
      }
    
      def tableName = "personas"
    }
    

    【讨论】:

    • JUnit 快照存储库在哪里?根据 build.xml github.com/KentBeck/junit/blob/master/build.xml#L306,我应该在 oss.sonatype.org/content/repositories/snapshots 找到它,但那里只有 4.9.1。
    • 我认为出现了错误。每晚构建似乎是 4.9.1 而不是 4.11-SNAPSHOT。我已经给维护者发了邮件。
    • 4.9.1-SNAPSHOT 不包含 ChainRule,因此它可能是 4.9.1 而不是 4.11。非常感谢您的帮助!
    • 抱歉,是的,我确定它正在构建 4.9.1。这是集成服务器上的配置问题。
    【解决方案2】:

    怎么样:

    abstract class DatabaseTest {
    
      // "TRUNCATE %s".format(tableName)
    
      def tableName
    }
    
    class PersonasTest extends DatabaseTest {
      @Before
      def addPersona() {
        // "INSERT INTO %s VALUES (...)".format(tableName)
      }
    
      @Test
      def testRejectsInsertWhenAlreadyInTable() {
        // "INSERT INTO %s VALUES (...)".format(tableName)
      }
    
      def tableName = "personas"
    }
    

    毕竟,PersonasTest is-a DatabaseTest.

    【讨论】: