for*_*ran 50 java enums unit-testing code-coverage mocking
我有一个或多或少像这样的枚举开关:
public static enum MyEnum {A, B}
public int foo(MyEnum value) {
switch(value) {
case(A): return calculateSomething();
case(B): return calculateSomethingElse();
}
throw new IllegalArgumentException("Do not know how to handle " + value);
}
Run Code Online (Sandbox Code Playgroud)
并且我希望测试涵盖所有行,但由于代码应该处理所有可能性,因此我无法在交换机中提供没有相应case语句的值.
扩展枚举以添加额外的值是不可能的,只是模拟返回的equals方法false
将无法工作,因为生成的字节码使用窗帘后面的跳转表来找到正确的情况......所以我想也许用PowerMock可以实现一些黑魔法.
谢谢!
编辑:
由于我拥有枚举,我认为我可以只为值添加一个方法,从而完全避免切换问题; 但是我要离开这个问题,因为它仍然很有趣.
Jon*_*eim 51
这是一个完整的例子.
代码几乎就像您的原始代码(只是简化了更好的测试验证):
public enum MyEnum {A, B}
public class Bar {
public int foo(MyEnum value) {
switch (value) {
case A: return 1;
case B: return 2;
}
throw new IllegalArgumentException("Do not know how to handle " + value);
}
}
Run Code Online (Sandbox Code Playgroud)
这里是完整代码覆盖的单元测试,测试适用于Powermock(1.4.10),Mockito(1.8.5)和JUnit(4.8.2):
@RunWith(PowerMockRunner.class)
public class BarTest {
private Bar bar;
@Before
public void createBar() {
bar = new Bar();
}
@Test(expected = IllegalArgumentException.class)
@PrepareForTest(MyEnum.class)
public void unknownValueShouldThrowException() throws Exception {
MyEnum C = PowerMockito.mock(MyEnum.class);
Whitebox.setInternalState(C, "name", "C");
Whitebox.setInternalState(C, "ordinal", 2);
PowerMockito.mockStatic(MyEnum.class);
PowerMockito.when(MyEnum.values()).thenReturn(new MyEnum[]{MyEnum.A, MyEnum.B, C});
bar.foo(C);
}
@Test
public void AShouldReturn1() {
assertEquals(1, bar.foo(MyEnum.A));
}
@Test
public void BShouldReturn2() {
assertEquals(2, bar.foo(MyEnum.B));
}
}
Run Code Online (Sandbox Code Playgroud)
结果:
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.628 sec
Run Code Online (Sandbox Code Playgroud)
小智 17
如果您可以使用 Maven 作为构建系统,则可以使用更简单的方法。只需在测试类路径中使用附加常量定义相同的枚举即可。
假设您在源目录 (src/main/java) 下声明了枚举,如下所示:
package my.package;
public enum MyEnum {
A,
B
}
Run Code Online (Sandbox Code Playgroud)
现在您在测试源目录 (src/test/java) 中声明完全相同的枚举,如下所示:
package my.package
public enum MyEnum {
A,
B,
C
}
Run Code Online (Sandbox Code Playgroud)
测试看到带有“重载”枚举的测试类路径,您可以使用“C”枚举常量测试您的代码。然后你应该看到你的 IllegalArgumentException 。
在 windows 下使用 maven 3.5.2、AdoptOpenJDK 11.0.3 和 IntelliJ IDEA 2019.3.1 进行测试
这是我的 Mockito 版本的 @Jonny Heggheim 的解决方案。它已经使用 Mockito 3.9.0 和 Java 11 进行了测试:
public class MyTestClass {
private static MockedStatic<MyEnum> myMockedEnum;
private static MyEnum mockedValue;
@BeforeClass
public void setUp() {
MyEnum[] newEnumValues = addNewEnumValue(MyEnum.class);
myMockedEnum = mockStatic(MyEnum.class);
myMockedEnum.when(MyEnum::values).thenReturn(newEnumValues);
mockedValue = newEnumValues[newEnumValues.length - 1];
}
@AfterClass
public void tearDown(){
myMockedEnum.close();
}
@Test
public void testCase(){
// Use mockedValue in your test case
...
}
private static <E extends Enum<E>> E[] addNewEnumValue(Class<E> enumClazz){
EnumSet<E> enumSet = EnumSet.allOf(enumClazz);
E[] newValues = (E[]) Array.newInstance(enumClazz, enumSet.size() + 1);
int i = 0;
for (E value : enumSet) {
newValues[i] = value;
i++;
}
E newEnumValue = mock(enumClazz);
newValues[newValues.length - 1] = newEnumValue;
when(newEnumValue.ordinal()).thenReturn(newValues.length - 1);
return newValues;
}
}
Run Code Online (Sandbox Code Playgroud)
使用此功能时请注意以下几点:
setup()
在 JVM 类加载器加载任何包含模拟 Enum 的 switch 语句的类之前,运行方法中的代码至关重要。如果您想知道原因,我建议您阅读@Vampire 的答案中引用的文章。@BeforeClass
。tearDown()
则可能会发生测试类中的测试成功,但随后在同一测试运行中运行时其他测试类中的测试失败的情况。这是因为MyEnum
保持延长直到您close()
致电MockedStatic
。setUp()
入tearDown()
单个测试用例中,我强烈建议使用 Robolectric 运行程序或任何其他测试运行程序运行测试,即保证每个测试用例都在新启动的 JVM 中运行。通过这种方式,您可以确保包含枚举 switch 语句的所有类都由类加载器为每个测试用例新加载。