Пересмешивание перечисления Java для добавления значения в тестовый регистр - PullRequest
47 голосов
/ 16 марта 2011

У меня есть переключатель enum , более или менее похожий на этот:

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 можно достичь какой-то черной магии.

Спасибо!

редактировать

Поскольку я владею перечислением, я подумал, что могу просто добавить метод к значениям и, таким образом, полностью избежать проблемы переключения; но я оставляю вопрос, так как он все еще интересен.

Ответы [ 8 ]

46 голосов
/ 29 августа 2011

Вот полный пример.

Код почти такой же, как ваш оригинал (только упрощенная проверка теста):

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
6 голосов
/ 23 мая 2013

@ Melloware

... код, который выполняет оператор switch (), java генерирует java.lang.ArrayIndexOutOfBounds ...

У меня такая же проблема,Запустите свой тест с новым Enum, как первым в своем классе испытаний.Я создал ошибку с этой проблемой: https://code.google.com/p/powermock/issues/detail?id=440

2 голосов
/ 13 апреля 2011

Как вы указали в своем редактировании, вы можете добавить функционал непосредственно в enum . Тем не менее, это может быть не лучшим вариантом, поскольку он может нарушать принцип «единой ответственности». Другим способом достижения этого является создание статической карты, которая содержит значения перечисления в качестве ключа и функциональность в качестве значения. Таким образом, вы можете легко проверить, имеет ли допустимое поведение для какого-либо значения перечисления циклы по всем значениям. В этом примере это может быть немного далеко, но я часто использую эту технику для сопоставления идентификаторов ресурсов с перечислением значений.

2 голосов
/ 16 марта 2011

Вместо того чтобы использовать некоторые радикальные манипуляции с байт-кодом, чтобы позволить тесту достичь последней строки в foo, я бы удалил его и вместо этого использовал бы статический анализ кода.Например, IntelliJ IDEA имеет проверку кода «оператор Enum switch, которая пропускает регистр», которая выдаст предупреждение для метода foo, если в нем отсутствует case.

1 голос
/ 17 июля 2013

jMock (по крайней мере начиная с версии 2.5.1, которую я использую) может сделать это из коробки. Вам нужно настроить Mockery на использование ClassImposterizer.

Mockery mockery = new Mockery();
mockery.setImposterizer(ClassImposterizer.INSTANCE);
MyEnum unexpectedValue = mockery.mock(MyEnum.class);
0 голосов
/ 08 февраля 2019

Я думаю, что самый простой способ получить исключение IllegalArgumentException - передать null методу foo, и вы прочтете «Не знаю, как обрабатывать ноль»

0 голосов
/ 28 апреля 2016

Прежде всего, Mockito может создавать фиктивные данные, которые могут быть целочисленными и т.д. Он не может создать правильный enum, так как enum имеет определенный номер порядкового имени значение и т. д., так что если у меня есть перечисление

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

так что у меня всего 5 порядковых в перечислении HttpMethod, но 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
0 голосов
/ 16 марта 2011

Я бы поставил регистр по умолчанию с одним из перечислений:

  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();
    }
  }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...