在 spock 测试中测试几个类似 类 的最佳方法?

Best approach to test several similar classes in spock test?

我有很多类似的 classes(实际上它是一个父 class 的不同类型的事件)。已经有大约 30 classes,而且这个数字还会增加。每个 class 都有自己的处理逻辑,但每个 class 中都存在多个字段。我想确保每个事件的流程都处理公共领域。它变得更加复杂,因为添加了新的事件类型和新的流程。最好的方法是创建一些动态测试来检查公共字段是否被处理。说 'dynamically' 我的意思是测试能够自动发现新的 classes 并将它们放入测试包中。我们正在使用 spock,但无法动态生成测试的 'where' 部分。我提出了一个很奇怪的方法,但它不起作用,但说明了我的想法:

def "dynamic test"() {
    given:
        def classes = methodToGetListOfEventClass()

    when:
        for(Class clazz : classes) {
                    ParentEvent event = clazz.getDeclaredConstructor().newInstance() as ParentEvent
                    service.sendEvent(event)
                }
            }

    then:
        for(Class clazz : classes) {
                    ParentEvent event = clazz.getDeclaredConstructor().newInstance() as ParentEvent
                    1 * sendExternalEvent("someId", event.getClass().getName(), Collections.emptyMap())
                    //check common fields exists
                    }
                }
            }

}

所以我只是尝试为每个 class 创建一个实例,将其传递给事件处理程序并检查创建的外部事件是否设置了所有公共字段。它看起来很丑而且不起作用。对如何实现这样的动态测试有什么建议吗?

您可以使用动态 data pipes。这是一个简单的示例,基于您的伪代码和您提供的有限信息。因为你没有说你是使用 Spock 1.3 还是 2.x,我确保该示例也适用于 1.3。

假设情况如下(所有Groovy代码,但被测试的classes也可以是Java):

interface Event {
  void init()
  void sendExternalEvent(String id, String className, Map options)
}
class Service {
  void sendEvent(Event event) {
    event.sendExternalEvent("123", event.class.name, [:])
  }
}
abstract class BaseEvent implements Event {
  private static final Random random = new Random()
  private static final String alphabet = (('A'..'Z') + ('0'..'9')).join()

  protected int id
  protected String name

  @Override
  void init() {
    id = 1 + random.nextInt(100)
    name = (1..10).collect { alphabet[random.nextInt(alphabet.length())] }.join()
  }
}
class FirstEvent extends BaseEvent {
  @Override
  void sendExternalEvent(String id, String className, Map options) {}
  String doFirst() { "first" }
}
class SecondEvent extends BaseEvent {
  @Override
  void sendExternalEvent(String id, String className, Map options) {}
  String doSecond() { "second" }
}
class ThirdEvent extends BaseEvent {
  @Override
  void sendExternalEvent(String id, String className, Map options) {}
  int doThird() { 3 }
}

您可以像这样对 BaseEvent 个子class 执行动态测试:

import spock.lang.Specification
import spock.lang.Unroll

class DynamicBaseClassTest extends Specification {
  @Unroll("verify #className")
  def "basic event class functionality"() {
    given:
    def service = new Service()
    def event = Spy(baseEventClass.getConstructor().newInstance())

    when:
    event.init()
    then:
    // '.id' and '.name' should be enough, but on Spock 2.1 there is a problem
    // when not explicitly using the '@' notation for direct field access.
    event.@id > 0
    event.@name.length() == 10

    when:
    service.sendEvent(event)
    then:
    1 * event.sendExternalEvent(_, event.class.name, [:])

    where:
    baseEventClass << getEventClasses()
    className = baseEventClass.simpleName
  }

  static List<Class<? extends BaseEvent>> getEventClasses() {
    [FirstEvent, SecondEvent, ThirdEvent]
  }
}

Try it in the Groovy web console.

值得注意的是:

    where:
    baseEventClass << getEventClasses()

数据管道被声明为调用数据提供程序方法,就像在您的示例中一样。 getEventClasses() 做什么,完全取决于您:return 一个固定列表,扫描 class 路径或其他。

    def event = Spy(baseEventClass.getConstructor().newInstance())

间谍必须具有被测 class 的真实行为——当然,你不想模拟它——以及稍后验证其交互的能力:

    then:
    1 * event.sendExternalEvent(_, event.class.name, [:])

顺便说一句,如果您不熟悉 @Unroll,它会使规范在 IDE 或测试报告中看起来像这样: