模拟Java枚举以添加值以测试失败案例

时间:2023-01-26 14:29:03

I have an enum switch more or less like this:

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

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);
}

and I'd like to have all the lines covered by the tests, but as the code is expected to deal with all possibilities, I cannot supply a value without its corresponding case statement in the switch.

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

Extending the enum to add an extra value is not possible, and just mocking the equals method to return false won't work either because the bytecode generated uses a jump table behind the curtains to go to the proper case... So I've thought that maybe some black magic could be achieved with PowerMock or something.

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

Thanks!

谢谢!

edit:

编辑:

As I own the enumeration, I've thought that I could just add a method to the values and thus avoid the switch issue completely; but I'm leaving the question as it's still interesting.

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

7 个解决方案

#1


39  

Here is a complete example.

这是一个完整的例子。

The code is almost like your original (just simplified better test validation):

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

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);
    }
}

And here is the unit test with full code coverage, the test works with Powermock (1.4.10), Mockito (1.8.5) and JUnit (4.8.2):

这是完整代码覆盖的单元测试,测试适用于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));
    }
}

Result:

结果:

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

#2


5  

@Melloware

@Melloware

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

...执行switch()语句的代码java抛出java.lang.ArrayIndexOutOfBounds ...

I have this same Problem. Run your test with new Enum as first in your Test Class. I created bug with this Problem: https://code.google.com/p/powermock/issues/detail?id=440

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

#3


2  

Rather than using some radical bytecode manipulation to enable a test to hit the last line in foo, I would remove it and rely on static code analysis instead. For example, IntelliJ IDEA has the "Enum switch statement that misses case" code inspection, which would produce a warning for the foo method if it lacked a case.

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

#4


2  

As you indicated in your edit, you can add the functionaliy in the enum itself. However, this might not be the best option, since it can violate the "One Responsibility" principle. Another way to achieve this is to create a static map which contains enum values as key and the functionality as value. This way, you can easily test if for any enum value you have a valid behavior by looping over all the values. It might be a bit far fetched on this example, but this is a technique I use often to map resource ids to enum values.

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

#5


1  

jMock (at least as of version 2.5.1 that I'm using) can do this out of the box. You will need to set your Mockery to use ClassImposterizer.

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

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

#6


0  

First of all Mockito can create mock data which can be integer long etc It cannot create right enum as enum has specific number of ordinal name value etc so if i have an enum

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

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

so i have total 5 ordinal in enum HttpMethod but mockito does not know it .Mockito creates mock data and its null all the time and you will end up in passing a null value . So here is proposed solution that you randomize the ordinal and get a right enum which can be passed for other test

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

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());       
            }
       }  
    }
}

Output :

输出:

0
GET
0
GET
5
PATCH
5
PATCH
4
HEAD
5
PATCH
3
DELETE
0
GET
4
HEAD
2
PUT

#7


-8  

I would put the default case with one of enum cases:

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

  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();
    }
  }

#1


39  

Here is a complete example.

这是一个完整的例子。

The code is almost like your original (just simplified better test validation):

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

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);
    }
}

And here is the unit test with full code coverage, the test works with Powermock (1.4.10), Mockito (1.8.5) and JUnit (4.8.2):

这是完整代码覆盖的单元测试,测试适用于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));
    }
}

Result:

结果:

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

#2


5  

@Melloware

@Melloware

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

...执行switch()语句的代码java抛出java.lang.ArrayIndexOutOfBounds ...

I have this same Problem. Run your test with new Enum as first in your Test Class. I created bug with this Problem: https://code.google.com/p/powermock/issues/detail?id=440

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

#3


2  

Rather than using some radical bytecode manipulation to enable a test to hit the last line in foo, I would remove it and rely on static code analysis instead. For example, IntelliJ IDEA has the "Enum switch statement that misses case" code inspection, which would produce a warning for the foo method if it lacked a case.

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

#4


2  

As you indicated in your edit, you can add the functionaliy in the enum itself. However, this might not be the best option, since it can violate the "One Responsibility" principle. Another way to achieve this is to create a static map which contains enum values as key and the functionality as value. This way, you can easily test if for any enum value you have a valid behavior by looping over all the values. It might be a bit far fetched on this example, but this is a technique I use often to map resource ids to enum values.

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

#5


1  

jMock (at least as of version 2.5.1 that I'm using) can do this out of the box. You will need to set your Mockery to use ClassImposterizer.

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

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

#6


0  

First of all Mockito can create mock data which can be integer long etc It cannot create right enum as enum has specific number of ordinal name value etc so if i have an enum

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

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

so i have total 5 ordinal in enum HttpMethod but mockito does not know it .Mockito creates mock data and its null all the time and you will end up in passing a null value . So here is proposed solution that you randomize the ordinal and get a right enum which can be passed for other test

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

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());       
            }
       }  
    }
}

Output :

输出:

0
GET
0
GET
5
PATCH
5
PATCH
4
HEAD
5
PATCH
3
DELETE
0
GET
4
HEAD
2
PUT

#7


-8  

I would put the default case with one of enum cases:

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

  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();
    }
  }