在 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>
我想使用 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>