#testing #groovy #overriding #runtime #spock
#тестирование #groovy #переопределение #время выполнения #спок
Вопрос:
Я попытался переопределить java.time.Общедоступный статический метод now() класса Year, потому что я хочу, чтобы он возвращал другое значение, а не только текущий год. В моем производительном коде у меня есть метод, который я тестирую, и он использует метод Year.now() как:
Тестируемый класс Java
public class SomeClass {
public LocalDate methodUnderTest() {
return Year.now()
.atMonth(JANUARY)
.atDay(1);
}
}
В тесте Spock groovy я попытался переопределить метод java.time.Year.now() таким образом:
Я предполагаю, что переопределенное поведение статического метода now() используется в моем производительном коде, в методе methodUnderTest()
class SomeClassSpec extends Specification {
@Subject
SomeClass classUnderTest = new SomeClass()
def "test method with overriden behaviour"() {
setup:
Year.metaClass.'static'.now = { -> Year.of(year) }
expect:
classUnderTest .methodUnderTest() == expectedDate
where:
year || expectedDate
2019 || LocalDate.of(2019, JANUARY, 1)
2020 || LocalDate.of(2020, JANUARY, 1)
2021 || LocalDate.of(2021, JANUARY, 1)
}
}
Однако тест работает только для 2020 года (текущего года), остальные 2 значения 2019 и 2021 не работают, потому что кажется, что предполагаемое переопределенное поведение явно не используется в производительном коде.
Почему предполагаемое переопределенное поведение не используется в производительном коде methodUnderTest() во время выполнения?
Ответ №1:
Некоторые основы:
- Метакласс Groovy работает только для классов Groovy, а не для классов Java. Т.Е. Класс Java ничего не знает о каких-либо определенных переопределениях метакласса, только Groovy, вызывающий ваш код класса Java.
- Класс начальной загрузки JRE
java.time.Year
являетсяfinal
, т. Е. Вы не можете обычными средствами создавать фиктивные экземпляры. Вам нужно будет отменить финализацию класса с помощью специального загрузчика классов при его загрузке. Мой собственный инструмент Sarek, который все еще находится в стадии разработки, предлагает эту функцию. Другие, такие как PowerMock, предоставляют более косвенные способы помочь вам заглушить конечные классы, используя инструменты классов, которые их вызывают. - Метод
Year.now()
, который вы хотите отключить, находится не только вfinal
классе, но иstatic
. У Spock нет встроенных средств для отключения статических методов, кроме методов Groovy. Опять же, ваш метод находится в классе Java и также вызывается классом Java, так что это вам не поможет. Опять же, Sarek или другие инструменты, такие как PowerMock, могут вам помочь.
Вот небольшой пример для выполнения этого в Sarek. Я только что отправил снимок в Maven Central для вас, так что вы должны быть в состоянии использовать это.
<dependency>
<groupId>dev.sarek</groupId>
<artifactId>sarek</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>dev.sarek</groupId>
<artifactId>sarek-spock-extension</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>
package de.scrum_master.stackoverflow.q65321086;
import java.time.LocalDate;
import java.time.Year;
import static java.time.Month.JANUARY;
public class SomeClass {
public LocalDate methodUnderTest() {
return Year.now()
.atMonth(JANUARY)
.atDay(1);
}
}
package de.scrum_master.stackoverflow.q65321086
import dev.sarek.agent.mock.MockFactory
import spock.lang.Specification
import spock.lang.Subject
import spock.lang.Unroll
import java.time.LocalDate
import java.time.Year
import static java.time.Month.JANUARY
import static net.bytebuddy.matcher.ElementMatchers.named
class SomeClassTest extends Specification {
@Subject
SomeClass classUnderTest = new SomeClass()
@Unroll
def "override static JRE method Year.now() for #year"() {
setup:
MockFactory<Year> mockFactory = MockFactory
.forClass(Year.class)
.mockStatic(
named("now"),
{ method, args -> false },
{ method, args, proceedMode, returnValue, throwable -> Year.of(year) }
)
.build()
expect:
classUnderTest.methodUnderTest() == expectedDate
cleanup:
mockFactory.close()
where:
year || expectedDate
2019 || LocalDate.of(2019, JANUARY, 1)
2020 || LocalDate.of(2020, JANUARY, 1)
2021 || LocalDate.of(2021, JANUARY, 1)
}
}
Этот тест проходит для меня. Для Sarek пока нет подробной документации или руководства, кроме довольно хорошего JavaDoc (проверьте исходные тексты) и множества примеров тестов в разных модулях.
Что касается Sarek, я планирую интегрировать его в Spock еще лучше, чем сейчас, поэтому его использование в будущем будет выглядеть как издевательство над «spocky». Я просто был очень занят в последнее время. Но он уже полностью пригоден для использования, а также поставляется с интеграциями в JUnit 4, Junit 5, TestNG.
Комментарии:
1. Обновление: ранее я забыл загрузить снимок Sarek в Maven Central, потому что у меня были некоторые проблемы с аутентификацией, которые теперь исправлены. Теперь все готово, если вы хотите использовать Sarek.