关于单元测试:模拟Java枚举以添加值来测试失败案例

Mocking Java enum to add a value to test fail case

我有一个或多或少像这样的枚举开关:

1
2
3
4
5
6
7
8
9
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);
}

并且我希望测试涵盖所有行,但由于代码应该处理所有可能性,因此我无法在交换机中提供没有相应case语句的值。

扩展枚举以添加额外的值是不可能的,只是模拟equals方法返回false也不会起作用,因为生成的字节码使用窗帘后面的跳转表来转到正确的情况......所以我 我认为可能用PowerMock或其他东西可以实现一些黑魔法。

谢谢!

编辑:

由于我拥有枚举,我认为我可以只为值添加一个方法,从而完全避免切换问题; 但是我要离开这个问题,因为它仍然很有趣。


这是一个完整的例子。

代码几乎就像您的原始代码(只是简化了更好的测试验证):

1
2
3
4
5
6
7
8
9
10
11
12
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);
    }
}

这里是完整代码覆盖的单元测试,测试适用于Powermock(1.4.10),Mockito(1.8.5)和JUnit(4.8.2):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@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));
    }
}

结果:

1
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.628 sec


@Melloware

... code that executes the switch() statement java throws a java.lang.ArrayIndexOutOfBounds ...

我有着同样的问题。使用新的Enum作为测试类中的第一个运行测试。我在这个问题上创建了错误:https://code.google.com/p/powermock/issues/detail?id = 440


正如您在编辑中指出的那样,您可以在枚举中添加功能。但是,这可能不是最佳选择,因为它可能违反"一个责任"原则。另一种实现此目的的方法是创建一个静态映射,其中包含枚举值作为键,功能作为值。这样,您可以通过循环遍历所有值,轻松测试是否对任何枚举值具有有效行为。在这个例子中可能有点牵强,但这是我经常用来将资源ID映射到枚举值的技术。


而不是使用一些激进的字节码操作来使测试能够到达foo中的最后一行,我会删除它并依赖静态代码分析。例如,IntelliJ IDEA具有"未命中案例"的"Enum switch语句"代码检查,如果缺少case,则会对foo方法产生警告。


jMock(至少从我使用的2.5.1版本开始)可以开箱即用。您需要将Mockery设置为使用ClassImposterizer。

1
2
3
Mockery mockery = new Mockery();
mockery.setImposterizer(ClassImposterizer.INSTANCE);
MyEnum unexpectedValue = mockery.mock(MyEnum.class);

我认为达??到IllegalArgumentException的最简单方法是将null传递给foo方法,你会读到"不知道如何处理null"


首先,Mockito可以创建可以是整数等的模拟数据
它不能创建正确的枚举,因为枚举具有特定数量的序号
价值等所以,如果我有一个枚举

1
2
3
public enum HttpMethod {
      GET, POST, PUT, DELETE, HEAD, PATCH;
}

所以我在enum HttpMethod中总共有5个序数但是mockito不知道它.Mockito一直创建模拟数据并且它的null并且你最终会传递一个空值。
所以这里提出的解决方案是你随机化序数并获得一个可以传递给其他测试的正确的枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
import static org.mockito.Mockito.mock;

import java.util.Random;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Matchers;
import org.mockito.internal.util.reflection.Whitebox;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import com.amazonaws.HttpMethod;




//@Test(expected = {"LoadableBuilderTestGroup"})
//@RunWith(PowerMockRunner.class)
public class testjava {
   // private static final Class HttpMethod.getClass() = null;
    private HttpMethod mockEnumerable;

    @Test
    public void setUpallpossible_value_of_enum () {
        for ( int i=0 ;i<10;i++){
            String name;
            mockEnumerable=    Matchers.any(HttpMethod.class);
            if(mockEnumerable!= null){
                System.out.println(mockEnumerable.ordinal());
                System.out.println(mockEnumerable.name());

                System.out.println(mockEnumerable.name()+"mocking suceess");
            }
            else {
                //Randomize all possible  value of  enum
                Random rand = new Random();
                int ordinal = rand.nextInt(HttpMethod.values().length);
                // 0-9. mockEnumerable=
                mockEnumerable= HttpMethod.values()[ordinal];
                System.out.println(mockEnumerable.ordinal());
                System.out.println(mockEnumerable.name());
            }
        }
    }







    @Test
    public void setUpallpossible_value_of_enumwithintany () {
        for ( int i=0 ;i<10;i++){
            String name;
            mockEnumerable=    Matchers.any(HttpMethod.class);
            if(mockEnumerable!= null){
                System.out.println(mockEnumerable.ordinal());
                System.out.println(mockEnumerable.name());

                System.out.println(mockEnumerable.name()+"mocking suceess");
            } else {
               int ordinal;
               //Randomize all possible  value of  enum
               Random rand = new Random();
               int imatch =  Matchers.anyInt();
               if(  imatch>HttpMethod.values().length)
                 ordinal = 0    ;
               else
                ordinal = rand.nextInt(HttpMethod.values().length);

               // 0-9.  mockEnumerable=
               mockEnumerable= HttpMethod.values()[ordinal];
               System.out.println(mockEnumerable.ordinal());
               System.out.println(mockEnumerable.name());      
            }
       }  
    }
}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
0
GET
0
GET
5
PATCH
5
PATCH
4
HEAD
5
PATCH
3
DELETE
0
GET
4
HEAD
2
PUT

我将默认情况与枚举案例之一:

1
2
3
4
5
6
7
8
9
10
11
12
13
  public static enum MyEnum {A, B}

  public int foo(MyEnum value) {
    if (value == null) throw new IllegalArgumentException("Do not know how to handle" + value);

    switch(value) {
        case(A):
           return calculateSomething();
        case(B):
        default:
           return calculateSomethingElse();
    }
  }