在 JUnit 参数化测试中使用非静态注入服务

Using non-static injected services in JUnit Parameterized Tests

我想使用 Guice 和 GuiceBerry 将非静态遗留服务注入工厂 class。然后我想将该工厂注入到我的参数化 JUnit 测试中。

但是,问题是 JUnit 要求 @Parameters 方法是静态的。

示例工厂:

@Singleton
public class Ratings {
    @Inject
    private RatingService ratingService;

    public Rating classicRating() {
         return ratingService.getRatingById(1002)
    }

    // More rating factory methods
}

示例测试用法:

@RunWith(Parameterized.class)
public class StaticInjectParamsTest {
    @Rule
    public GuiceBerryRule guiceBerryRule = new GuiceBerryRule(ExtendedTestMod.class)

    @Inject
    private static Ratings ratings;

    @Parameter
    public Rating rating;

    @Parameters
    public static Collection<Rating[]> ratingsParameters() {
    return Arrays.asList(new Rating[][]{
            {ratings.classicRating()}
            // All the other ratings
        });
    }

    @Test
    public void shouldWork() {
        //Use the rating in a test

    }
}

我已经尝试为工厂方法请求静态注入,但是 Parameters 方法在 GuiceBerry @Rule 之前被调用。我也考虑过只使用评级的 Id 作为参数,但我想找到一个可重用的解决方案。也许我的方法有缺陷?

不幸的是,JUnit 需要能够在 运行 任何测试之前枚举所有测试,因此必须在规则之前调用参数方法。

您可以定义评级类型的枚举:

@RunWith(Parameterized.class)
public class StaticInjectParamsTest {
  @Rule
  public GuiceBerryRule guiceBerryRule
      = new GuiceBerryRule(ExtendedTestMod.class);

  @Inject
  private Ratings ratings;

  @Parameter
  public RatingType ratingType;

  @Parameters
  public static Collection<RatingType> types() {
    return Arrays.asList(RatingType.values());
  }

  @Test
  public void shouldWork() {
    Rating rating = ratings.get(ratingType);
    // Use the rating in a test
  }
}

编辑:枚举代码:

public enum RatingType {
  CLASSIC(1002),
  COMPLEX(1020);

  private final int ratingId;

  private RatingType(int ratingId) {
    this.ratingId = ratingId;
  }

  // option 1: keep rating ID private by having a method like this
  public get(RatingService ratingService) {
    return ratingService.getRatingById(ratingId);
  }

  // option 2: have a package-scope accessor
  int getRatingId() {
    return ratingId;
  }
}

编辑: 如果您选择选项 2,您将添加一个新方法从 RatingType 中获取 Rating,该方法将委托给服务传递 ratingId

@Singleton
public class Ratings {
    @Inject
    private RatingService ratingService;

    public Rating getRating(RatingType ratingType) {
      return ratingService.getRatingById(
          ratingType.getRatingId());
    }

    // More rating factory methods
}

如果你不希望 RatingType 在你的 public API 中,你可以在你的测试中定义它,并在枚举中有一个名为 [=18 的方法=]

public enum RatingType {
  CLASSIC {
    @Override public Rating getRating(Ratings ratings) {
      return ratings.getClassicRating();
    }
  },
  COMPLEX {
    @Override public Rating getRating(Ratings ratings) {
      return ratings.getComplexRating();
    }
  };

  public abstract Rating getRating(Ratings ratings);
}

您还可以创建值类型而不是枚举。

这假设您可以编写应该​​通过所有 Rating 个实例的测试。

如果你有一些常见的测试,但 一些特定评级的测试,我会做一个抽象基础class包含常见的测试和一个抽象的 createRating() 方法,以及针对每个评级类型的子class 它。

我没有得到 guiceberry 到 运行(古老的依赖项),但是使用 JUnitParamters 和普通的 guice,这很简单:

@RunWith(JUnitParamsRunner.class)
public class GuiceJunitParamsTest {

    public static class SquareService {
        public int calculate(int num) {
            return num * num;
        }
    }

    @Inject
    private SquareService squareService;

    @Before
    public void setUp() {
        Guice.createInjector().injectMembers(this);
    }

    @Test
    @Parameters({ "1,1", "2,4", "5,25" })
    public void calculateSquares(int num, int result) throws Exception {
        assertThat(squareService.calculate(num), is(result));
    }
}

如果您查看 JUnitParams 网站,您会发现许多其他方法来定义参数列表。使用注入服务很容易做到这一点。

我的解决方案是添加一个 RatingId class 来包装一个整数并创建一个工厂 RatingIds 然后我可以 return 静态并将其用作参数。我在我的 RatingService 接口中重载了 getRatingById 方法以接受新的 RatingId 类型,然后将评级服务注入我的测试并直接使用它。

添加工厂:

public class RatingIds {
    public static RatingId classic() {
        return new RatingId(1002);
    }
    // Many more
}

测试:

@RunWith(Parameterized.class)
public class StaticInjectParamsTest {
    @Rule
    public GuiceBerryRule guiceBerryRule = new GuiceBerryRule(ExtendedTestMod.class)

    @Inject
    private RatingService ratingService

    @Parameter
    public RatingId ratingId;

    @Parameters
    public static Collection<RatingId[]> ratingsParameters() {
    return Arrays.asList(new RatingId[][]{
        {RatingIds.classic()}
        // All the other ratings
        });
    }

    @Test
    public void shouldWork() {
        Rating rating = ratingService.getRatingById(ratingId.getValue())
        //Use the rating in a test

    }
}

在像您这样的情况下,生成的参数集的总数是事先已知的,但是构建参数本身需要一些上下文(例如,使用 Spring 自动装配的服务实例),您可以采用函数式方法(使用 junit5 和参数化)

显然这不起作用,如果 createParameter 函数本身依赖于这样的上下文:-/

class MyTestClass {

    // may be autowired, cannot be static but is required in parameter generation
    SomeInstance instance;

    private interface SomeParamBuilder { SomeParam build(SomeInstance i);}

    private static Stream<Arguments> createParamterFactories() {
         return Stream.of(
            Arguments.of((SomeParamBuilder)(i)->     
                            {
                                return new SomeParam(i);
                            })
                         );
    }

    // does not work, because SomeParam needs SomeInstance for construction
    // which is not available in static context of createParameters.
    //@ParameterizedTest(name = "[{index}] {0}")
    //@MethodSource("createParameters")
    //void myTest(SomeParam param) {
    //}


    @ParameterizedTest(name = "[{index}] {0}")
    @MethodSource("createParamterFactories")
    void myTest(SomeParamBuilder builder) {
        SomeParam param = builder.build(instance);
        // rest of your test code can use param.
    }
}

maven 部门:

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-params</artifactId>
            <version>5.2.0</version>
            <scope>test</scope>
        </dependency>