Статьи

Тестирование Kotlin с помощью Спока. Часть 2. Перечисление методом экземпляра.

Класс enum с методом instance в Kotlin очень похож на его версию Java, но они выглядят немного иначе в байт-коде. Давайте посмотрим на разницу, написав несколько тестов, используя
Спок

Что мы хотим проверить?

Давайте посмотрим код, который мы хотим протестировать:

01
02
03
04
05
06
07
08
09
10
enum class EnumWithInstanceMethod {
    PLUS {
        override fun sign(): String = "+"
    },
    MINUS {
        override fun sign(): String = "-"
    };
 
    abstract fun sign(): String
}

Очевидно, что это может быть написано лучше (например, с помощью переменной экземпляра enum), но этот пример показывает случай, который мы хотим протестировать самым простым способом.

Как это проверить со Споком?

Самый простой тест (который не работает)

Во-первых, мы можем написать тест, как если бы мы делали это с помощью перечисления Java:

1
2
3
4
def "should use enum method like in java"() {
    expect:
        EnumWithInstanceMethod.MINUS.sign() == '-'
}

Тест не пройден:

01
02
03
04
05
06
07
08
09
10
11
12
Condition failed with Exception:
 
EnumWithInstanceMethod.MINUS.sign() == '-'
                             |
                             groovy.lang.MissingMethodException: No signature of method: static com.github.alien11689.testingkotlinwithspock.EnumWithInstanceMethod$MINUS.sign() is applicable for argument types: () values: []
                             Possible solutions: sign(), sign(), is(java.lang.Object), find(), with(groovy.lang.Closure), find(groovy.lang.Closure)
 
 
    at com.github.alien11689.testingkotlinwithspock.EnumWithInstanceMethodTest.should use enum method like in java(EnumWithInstanceMethodTest.groovy:11)
Caused by: groovy.lang.MissingMethodException: No signature of method: static com.github.alien11689.testingkotlinwithspock.EnumWithInstanceMethod$MINUS.sign() is applicable for argument types: () values: []
Possible solutions: sign(), sign(), is(java.lang.Object), find(), with(groovy.lang.Closure), find(groovy.lang.Closure)
    ... 1 more

Интересно … Почему Groovy говорит нам, что мы пытаемся вызвать статический метод? Может быть, мы используем не экземпляр enum, а что-то еще? Давайте создадим тест, в котором мы передадим экземпляр enum методу:

1
2
3
4
5
6
7
8
static String consume(EnumWithInstanceMethod e) {
    return e.sign()
}
 
def "should pass enum as parameter"() {
    expect:
        consume(EnumWithInstanceMethod.MINUS) == '-'
}

Сообщение об ошибке:

01
02
03
04
05
06
07
08
09
10
11
12
Condition failed with Exception:
 
consume(EnumWithInstanceMethod.MINUS) == '-'
|
groovy.lang.MissingMethodException: No signature of method: static com.github.alien11689.testingkotlinwithspock.EnumWithInstanceMethodTest.consume() is applicable for argument types: (java.lang.Class) values: [class com.github.alien11689.testingkotlinwithspock.EnumWithInstanceMethod$MINUS]
Possible solutions: consume(com.github.alien11689.testingkotlinwithspock.EnumWithInstanceMethod)
 
 
    at com.github.alien11689.testingkotlinwithspock.EnumWithInstanceMethodTest.should pass enum as parameter(EnumWithInstanceMethodTest.groovy:29)
Caused by: groovy.lang.MissingMethodException: No signature of method: static com.github.alien11689.testingkotlinwithspock.EnumWithInstanceMethodTest.consume() is applicable for argument types: (java.lang.Class) values: [class com.github.alien11689.testingkotlinwithspock.EnumWithInstanceMethod$MINUS]
Possible solutions: consume(com.github.alien11689.testingkotlinwithspock.EnumWithInstanceMethod)
    ... 1 more

Теперь мы видим, что мы прошли класс
com.github.alien11689.testingkotlinwithspock.EnumWithInstanceMethod$MINUS , а не экземпляр enum.

Но это работает на Java …

Аналогичный код в JUnit работает отлично, и тест проходит:

1
2
3
4
@Test
public void shouldReturnSign() {
    assertEquals("-", EnumWithInstanceMethod.MINUS.sign());
}

Java может получить доступ к методу экземпляра Kotlin без проблем, поэтому, возможно, что-то не так с Groovy…

Но перечисление Java с методом экземпляра, например,

01
02
03
04
05
06
07
08
09
10
11
12
13
14
public enum EnumWithInstanceMethodInJava {
    PLUS {
        public String sign() {
            return "+";
        }
    },
    MINUS {
        public String sign() {
            return "-";
        }
    };
 
    public abstract String sign();
}

правильно работает в тесте Спока:

1
2
3
4
def "should use enum method"() {
    expect:
        EnumWithInstanceMethodInJava.MINUS.sign() == '-'
}

Какая разница?

Мы можем заметить разницу, просто посмотрев на скомпилированные классы:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
$ tree build/classes/main/
build/classes/main/
└── com
    └── github
        └── alien11689
            └── testingkotlinwithspock
                ├── AdultValidator.class
                ├── EnumWithInstanceMethod.class
                ├── EnumWithInstanceMethodInJava$1.class
                ├── EnumWithInstanceMethodInJava$2.class
                ├── EnumWithInstanceMethodInJava.class
                ├── EnumWithInstanceMethod$MINUS.class
                ├── EnumWithInstanceMethod$PLUS.class
                ├── Error.class
                ├── Ok.class
                ├── ValidationStatus.class
                └── Validator.class

Java генерирует анонимные классы (
EnumWithInstanceMethodInJava$1 и
EnumWithInstanceMethodInJava$2 ) для экземпляров перечисления, но Kotlin называет эти классы после имен экземпляров перечисления (
EnumWithInstanceMethod$MINUS и
EnumWithInstanceMethod$PLUS ).

Как это связано с проблемой с Groovy? Groovy не нуждается в
.class при доступе к классу в коде, поэтому, когда мы пытаемся получить доступ к
EnumWithInstanceMethod.MINUS , Groovy преобразует его в
EnumWithInstanceMethod.MINUS.class , а не экземпляр перечисления. Та же проблема не возникает в коде Java, так как нет
EnumWithInstanceMethodInJava$MINUS класс.

Решение

Зная разницу, мы можем решить проблему доступа к экземпляру перечисления Kotlin в нашем коде Groovy.

Первое решение — доступ к экземпляру enum с
Метод valueOf :

1
2
3
4
def "should use enum method working"() {
    expect:
        EnumWithInstanceMethod.valueOf('MINUS').sign() == '-'
}

Второй способ — явно сказать Groovy, что мы хотим получить доступ к статическому полю, которое является экземпляром enum:

1
2
3
4
def "should use enum method"() {
    expect:
        EnumWithInstanceMethod.@MINUS.sign() == '-'
}

Вы можете выбрать любое решение в зависимости от стиля вашего кода и ваших предпочтений.

Покажи мне код

Код доступен здесь .

Смотрите оригинальную статью здесь: Тестирование Kotlin с помощью Спока. Часть 2. Enum с методом экземпляра.

Мнения, высказанные участниками Java Code Geeks, являются их собственными.