嘲讽的Java枚举添加一个值来测试失败的情况下

问题描述:

我有一个枚举开关或多或少是这样的:嘲讽的Java枚举添加一个值来测试失败的情况下

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

的IlegalArgument可从来没有因为明确的性能被抛出的枚举,但是你有什么要混淆你的代码来测试它将处理不可能的事情?如果你真的想要拜拜你的生产线速度指标,为什么不删除永远不能执行的生产线呢? – Raedwald 2013-08-22 22:27:03

+5

@Raedwald理由2:首先,其他人可能会为枚举创建一个新值,并忘记为该开关添加一个新案例;其次,在切换之后,如果没有'throw'或'return',代码将不会编译。 – fortran 2013-08-23 10:17:22

+0

经过考虑,我认为只是让它没有经过测试。没有非法的枚举值来触发异常,并且嘲笑是很痛苦的。我认为投球很好,这是未来的证明,只是很难测试。不值得费力测试,恕我直言。 – 2016-09-20 21:51:43

我把默认的情况下,用枚举情况之一:

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

这不是所需的行为,当添加新的枚举值时它会产生意外的结果。 – fortran 2011-03-16 10:40:39

而不是使用一些过激的字节码操作,以使测试打到最后一行foo,我会删除它,并依赖于静态代码分析。例如,IntelliJ IDEA具有“Enum switch未执行大小写”代码检查的声明,如果缺少case,则会为foo方法发出警告。

+1

这就是为什么我把失败的行动之外的“默认”的情况下,允许静态分析过......但我不愿意删除运行时检查,只是依靠静态的分析,我认为两者应该是相辅相成。 – fortran 2011-03-18 11:20:26

+0

这是我的观点,使用静态分析来补充测试套件+代码覆盖率。如果使用它,那么带'throw'语句的行会变得多余并且可以被删除,因为IDE/build中会检测到'switch'中缺少'case'。 – 2011-03-18 13:19:44

+0

我认为这是一个好主意。枚举的默认情况并不是真正的代码,因为它运行的应用程序的一部分,它的代码将来可能会违反可能会引入错误的错误。最好只编写你的应用程序,并确保它是正确的。 Sonar是否存在“未命中的枚举switch语句”规则? – user2800708 2015-11-20 09:08:23

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

这是一个完整的例子。

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

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):

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

结果:

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

我跟着使用1.9.0的Mockito和PowerMock 1.4.12你的榜样,我能够在执行开关()语句java的抛出一个异常java.lang.ArrayIndexOutOfBounds喜欢它知道的代码注入新的枚举进入我的列表但是不应该有额外的一个。有什么想法吗? – Melloware 2012-07-20 22:01:35

+3

如果有人遇到@Melloware遇到的问题,以下内容可能会有用。为了让上面的例子能够在我自己的测试中工作,我必须添加原始枚举的所有值,并且将模拟的值添加到when/thenReturn语句中,并正确设置序号。如果你嘲笑一个额外的值,那么序号应该是你原始的unmocked枚举值的数量。 – JeroenHoek 2013-10-04 21:23:41

+2

你怎么能PowerMockito.mockStatic(MyEnum.class);?它应该给java.lang.IllegalArgumentException异常:不能继承final类 – Thamiar 2015-11-20 08:59:16

@Melloware

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

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

+2

当我使用@PrepareForTest MyEnum.class)在方法级别。 – 2013-05-22 23:38:11

JMock的(至少为2.5.1,我使用的版本)可以做到这一点的开箱。您将需要设置您的Mockery以使用ClassImposterizer。

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

所有的Mockito首先可以创建可整数长等优点 它不能建立正确的枚举的枚举有序号名称 值等的具体数目,所以如果我有一个枚举模拟数据

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

所以我有枚举列举HTTPMethod共5序,但不的Mockito不知道它.Mockito创建模拟数据和空所有的时间,你会在传递一个空值结束。 所以在这里建议你随机的顺序,并获得可以用于其他测试传递一个正确的枚举的解决方案

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

输出:

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