Класс 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, являются их собственными. |