如何在JUnit 5中对beforeEach()进行参数化?

24

我正在使用JUnit 5作为我的测试运行器。

在设置方法中,我已经硬编码了3个参数(platformName, platformVersion, 和 deviceName)。我有一个测试方法应该测试各种组合……这意味着,在运行我的testLogin()测试时,它应该在多个平台名称、版本、设备名称上运行……

所以,我尝试了以下代码...

@BeforeEach
@CsvSource({"IOS,13.0,iPhone X Simulator", "IOS,13.2,iPhone Simulator", "IOS,13.3,iPhone XS Simulator"})
void setUp(String platformName, String platformVersion, String deviceName) throws MalformedURLException {
    ....
    capabilities.setCapability("platformName", platformName);
    capabilities.setCapability("platformVersion", platformVersion);
    capabilities.setCapability("deviceName", deviceName);
    capabilities.setCapability("methodName", testInfo.getDisplayName());
}

我的问题是,如何将beforeEach()方法参数化?同时,我想获取测试方法的名称... 如果我指定了参数,那么在哪里应该指定TestInfo参数。

请帮助我。我也看到了以下问题...

JUnit 5中参数化beforeEach/beforeAll

========

public class TestBase {

    @BeforeEach
    void setUp(TestInfo testInfo) throws MalformedURLException {
        MutableCapabilities capabilities = new MutableCapabilities();
        capabilities.setCapability("platformName", "iOS");
        capabilities.setCapability("platformVersion", "13.2");
        capabilities.setCapability("deviceName", "iPhone Simulator");
        capabilities.setCapability("name", testInfo.getDisplayName());
        capabilities.setCapability("app", “/home/my-user/testapp.zip");

        driver = new IOSDriver(
                new URL("https://192.168.1.4:5566/wd/hub"),
                capabilities
        );
    }
}
public class LoginTest extends TestBase {

    @Test
    public void testLogin() {
        driver.findElement(By.id("user-name")).sendKeys(“myuser);
        driver.findElement(By.id("password")).sendKeys(“mypassword);
        driver.findElement(By.id(“login_btn”)).click();
        assertTrue(true);
    }
}
3个回答

8

让我们试试@lamektomasz提供的绝妙的解决方案

  • 创建文件CustomParameterResolver.java以解析@BeforeEach@AfterEach注释的参数。
package com.example;

import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Optional;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.api.extension.ParameterResolver;
import org.junit.jupiter.engine.execution.BeforeEachMethodAdapter;
import org.junit.jupiter.engine.extension.ExtensionRegistry;

public class CustomParameterResolver implements BeforeEachMethodAdapter, ParameterResolver {

  private ParameterResolver parameterisedTestParameterResolver = null;

  @Override
  public void invokeBeforeEachMethod(ExtensionContext context, ExtensionRegistry registry)
      throws Throwable {
    Optional<ParameterResolver> resolverOptional = registry.getExtensions(ParameterResolver.class)
        .stream()
        .filter(parameterResolver ->
            parameterResolver.getClass().getName()
                .contains("ParameterizedTestParameterResolver")
        )
        .findFirst();
    if (!resolverOptional.isPresent()) {
      throw new IllegalStateException(
          "ParameterizedTestParameterResolver missed in the registry. Probably it's not a Parameterized Test");
    } else {
      parameterisedTestParameterResolver = resolverOptional.get();
    }
  }

  @Override
  public boolean supportsParameter(ParameterContext parameterContext,
      ExtensionContext extensionContext) throws ParameterResolutionException {
    if (isExecutedOnAfterOrBeforeMethod(parameterContext)) {
      ParameterContext pContext = getMappedContext(parameterContext, extensionContext);
      return parameterisedTestParameterResolver.supportsParameter(pContext, extensionContext);
    }
    return false;
  }

  @Override
  public Object resolveParameter(ParameterContext parameterContext,
      ExtensionContext extensionContext) throws ParameterResolutionException {
    return parameterisedTestParameterResolver.resolveParameter(
        getMappedContext(parameterContext, extensionContext), extensionContext);
  }

  private MappedParameterContext getMappedContext(ParameterContext parameterContext,
      ExtensionContext extensionContext) {
    return new MappedParameterContext(
        parameterContext.getIndex(),
        extensionContext.getRequiredTestMethod().getParameters()[parameterContext.getIndex()],
        Optional.of(parameterContext.getTarget()));
  }

  private boolean isExecutedOnAfterOrBeforeMethod(ParameterContext parameterContext) {
    return Arrays.stream(parameterContext.getDeclaringExecutable().getDeclaredAnnotations())
        .anyMatch(this::isAfterEachOrBeforeEachAnnotation);
  }

  private boolean isAfterEachOrBeforeEachAnnotation(Annotation annotation) {
    return annotation.annotationType() == BeforeEach.class
        || annotation.annotationType() == AfterEach.class;
  }
}
  • 创建MappedParameterContext.java文件
package com.example;

import java.lang.annotation.Annotation;
import java.lang.reflect.Parameter;
import java.util.List;
import java.util.Optional;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.platform.commons.util.AnnotationUtils;

public class MappedParameterContext implements ParameterContext {

  private final int index;
  private final Parameter parameter;
  private final Optional<Object> target;

  public MappedParameterContext(int index, Parameter parameter,
      Optional<Object> target) {
    this.index = index;
    this.parameter = parameter;
    this.target = target;
  }

  @Override
  public boolean isAnnotated(Class<? extends Annotation> annotationType) {
    return AnnotationUtils.isAnnotated(parameter, annotationType);
  }

  @Override
  public <A extends Annotation> Optional<A> findAnnotation(Class<A> annotationType) {
    return Optional.empty();
  }

  @Override
  public <A extends Annotation> List<A> findRepeatableAnnotations(Class<A> annotationType) {
    return null;
  }

  @Override
  public int getIndex() {
    return index;
  }

  @Override
  public Parameter getParameter() {
    return parameter;
  }

  @Override
  public Optional<Object> getTarget() {
    return target;
  }
}
  • 将解析器添加到您的测试项目中
package com.example;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

@ExtendWith(CustomParameterResolver.class)
public class BaseTest {

  @BeforeEach
  public void beforeEach(String platformName, String platformVersion, String deviceName) {
    System.out.println("Before each:");
    System.out.println("platformName: " + platformName);
    System.out.println("platformVersion: " + platformVersion);
    System.out.println("deviceName: " + deviceName);
  }

  @ParameterizedTest
  @CsvSource({"IOS,13.0,iPhone X Simulator", "IOS,13.2,iPhone Simulator", "IOS,13.3,iPhone XS Simulator"})
  void testLogin(String platformName, String platformVersion, String deviceName) {
    // ...
    capabilities.setCapability("platformName", platformName);
    capabilities.setCapability("platformVersion", platformVersion);
    capabilities.setCapability("deviceName", deviceName);
    capabilities.setCapability("methodName", testInfo.getDisplayName());
  }
}

  • 输出:
Before each:
platformName: IOS
platformVersion: 13.0
deviceName: iPhone X Simulator
===========
Before each:
platformName: IOS
platformVersion: 13.2
deviceName: iPhone Simulator
===========
Before each:
platformName: IOS
platformVersion: 13.3
deviceName: iPhone XS Simulator
===========

这种方法的问题在于,如果一个人应用了@ExtendWith(CustomParameterResolver.class),那么他“必须”使用@ParameterizedTest。这可能看起来很明显,但是想象一下:我有一个用于测试的基类,在其中我会使用@ExtendWith(CustomParameterResolver.class)进行扩展,只是为了将“一些”派生测试类转换为参数化测试。这是不可能的。现在我必须将所有派生类都转换为使用参数化测试。我是对的吗?有什么解决方案/调整吗?(我是Java新手) - rahman
@rahman 如果你的所有测试都不需要参数化,那么将参数化放在 @BeforeEach 中肯定是错误的。根据 jumb0jet 的回答,只对需要参数化的测试进行适当的参数化。 - Matthew Read

8

无法对@BeforEach方法进行参数化。JUnit5仅支持参数化测试(测试方法)。

参数化测试的声明方式与普通的@Test方法相同,但使用@ParameterizedTest注释来替代。此外,你必须至少声明一个来源(例如@CsvSource@ValueSource等)。

例如:

    @ParameterizedTest
    @CsvSource({
        "apple,         1",
        "banana,        2",
        "'lemon, lime', 0xF1"
    })
    void testWithCsvSource(String fruit, int rank) {
        assertNotNull(fruit);
        assertNotEquals(0, rank);
    }

4
Javadocs规е®ҡ@BeforeEachж–№жі•еҝ…йЎ»е…·жңүvoidиҝ”еӣһзұ»еһӢпјҢдёҚиғҪжҳҜз§Ғжңүзҡ„пјҢ并且дёҚиғҪжҳҜйқҷжҖҒзҡ„гҖӮе®ғ们еҸҜд»ҘйҖүжӢ©еЈ°жҳҺиҰҒз”ұParameterResolversи§Јжһҗзҡ„еҸӮж•°гҖӮ - Eric B.

1

据我所知,在Junit5中,无法对@BeforeEach进行参数化。

这可以通过一个新的类级别参数化功能来实现,这似乎是一个非常受欢迎的功能,但在我写下这些文字时还不可用。请参阅未来可能的样子

在等待期间,我知道有3种解决方法:

  1. 使用自定义参数解析器扩展,
  2. 使用@TestFactory动态生成测试方法,
  3. 使用抽象类+具体类。

有关这3种方法的更多详细信息,请参阅:https://github.com/junit-team/junit5/issues/3157#issuecomment-1435009129


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