Зависимость Objenesis вызывает ошибку инстанцирования - PullRequest
1 голос
/ 18 марта 2020

Просто запускаю новый проект Gradle.

Этот тест проходит:

def 'Launcher.main should call App.launch'(){
    given:
    GroovyMock(Application, global: true)

    when:
    Launcher.main()

    then:
    1 * Application.launch( App, null ) >> null
}

... до тех пор, пока, чтобы получить другой тест с использованием (Java) Mock для работы, я нужно добавить следующие зависимости:

testImplementation 'net.bytebuddy:byte-buddy:1.10.8'
testImplementation 'org.objenesis:objenesis:3.1'

(NB Я предполагаю, что эти версии в порядке для Groovy 3. +, который я сейчас использую ... обе являются наиболее современными из доступных в Maven Repo).

С этими зависимостями вышеописанный тест не пройден:

java.lang.InstantiationError: javafx.application.Application
    at org.objenesis.instantiator.sun.SunReflectionFactoryInstantiator.newInstance(SunReflectionFactoryInstantiator.java:48)
    at org.objenesis.ObjenesisBase.newInstance(ObjenesisBase.java:73)
    at org.objenesis.ObjenesisHelper.newInstance(ObjenesisHelper.java:44)
    at org.spockframework.mock.runtime.MockInstantiator$ObjenesisInstantiator.instantiate(MockInstantiator.java:45)
    at org.spockframework.mock.runtime.MockInstantiator.instantiate(MockInstantiator.java:31)
    at org.spockframework.mock.runtime.GroovyMockFactory.create(GroovyMockFactory.java:57)
    at org.spockframework.mock.runtime.CompositeMockFactory.create(CompositeMockFactory.java:42)
    at org.spockframework.lang.SpecInternals.createMock(SpecInternals.java:47)
    at org.spockframework.lang.SpecInternals.createMockImpl(SpecInternals.java:298)
    at org.spockframework.lang.SpecInternals.createMockImpl(SpecInternals.java:288)
    at org.spockframework.lang.SpecInternals.GroovyMockImpl(SpecInternals.java:215)
    at core.AppSpec.Launcher.main should call App.launch(first_tests.groovy:30)

Признаюсь, у меня есть только самое схематичное представление о том, что на самом деле делают "bytebuddy" и "objenesis", хотя Я предполагаю, что это чертовски умно. Изменить: только что посетив их соответствующие домашние страницы, мое представление теперь немного менее схематично, и да, это чертовски умно.

Если ортодоксальное решение этого не доступно, возможно ли вообще отключить использование этих зависимостей для отдельной функции (т. е. теста)? Возможно, с помощью какой-нибудь аннотации, может быть?

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

Это MCVE: Характеристики: Java 11.0.5, ОС Linux Mint 18.3.

build.gradle:

plugins {
    id 'groovy'
    id 'java'
    id 'application'
    id 'org.openjfx.javafxplugin' version '0.0.8'
}
repositories { mavenCentral() }
javafx {
    version = "11.0.2"
    modules = [ 'javafx.controls', 'javafx.fxml' ]
}
dependencies {
    implementation 'org.codehaus.groovy:groovy:3.+'
    testImplementation 'junit:junit:4.12'
    testImplementation 'org.spockframework:spock-core:2.0-M2-groovy-3.0'
    testImplementation 'net.bytebuddy:byte-buddy:1.10.8'
    testImplementation 'org.objenesis:objenesis:3.1'
    // in light of kriegaex's comments:
    implementation group: 'cglib', name: 'cglib', version: '3.3.0'
}
test { useJUnitPlatform() }
application {
    mainClassName = 'core.Launcher'
}
installDist{}

main. groovy:

class Launcher {
    static void main(String[] args) {
        Application.launch(App, null )
    }
}
class App extends Application {
    void start(Stage primaryStage) {
    }
}

first_tests. groovy:

class AppSpec extends Specification {
    def 'Launcher.main should call App.launch'(){
        given:
        GroovyMock(Application, global: true)
        when:
        Launcher.main()
        then:
        1 * Application.launch( App, null ) >> null
    }
}

Причина, по которой этому проекту нужно что-то для вызова подкласса Application, объясняется здесь : так можно сделать installDist, который связывает в JavaFX.

1 Ответ

1 голос
/ 20 марта 2020

Разве нам не нужно использовать глобальный GroovyMock?

Если вы хотите проверить взаимодействие, да. Но на самом деле вы тестируете средство запуска JavaFX, а не свое приложение. Поэтому я сомневаюсь, что есть какая-то польза. Вместо этого я бы сосредоточился на тестировании класса App. Также представьте на минуту, что вы будете писать классы с основными методами в Java вместо Groovy. Groovy mocks не будут работать при вызове из кода Java, особенно не глобальных. Затем вы закончите тестирование с помощью Powermockito от Spock, которое также будет работать, но вы все равно протестируете средство запуска JavaFX, а не свое приложение.

Кроме того, не слишком ли экстремально говорить любой использование Groovy издевательств не так?

Я этого не говорил. Я сказал: « вероятно что-то не так с дизайном вашего приложения». Причина, по которой я это сказал, заключается в том, что использование Groovy mocks и таких методов, как mocking stati c, - это запах тестового кода. Вы можете проверить запах, а затем решить, что все в порядке, что в большинстве случаев не так. Кроме того, проблема заключается не только в разработке приложения, но и в самом тесте, что в данном случае я бы сказал. Но это спорно, так что я собираюсь представить решение вам ниже.

1021 * В этом случае технически глобальной Application фиктивный ваш единственный путь, если вы настаиваете, чтобы проверить пусковую JavaFX, потому что даже global mock на App не будет работать, так как программа запуска использует отражение для вызова конструктора App, и это не перехватывается фреймворком.

вы говорите, что Spock spock-core: 2.0-M2- groovy -3.0 является "предварительным выпуском". Я не вижу ничего на этой странице (...), что говорит это. Откуда вы знаете?

Вы уже узнали об этом, проверив репозиторий GitHub, но я просто видел его в необычном номере версии, содержащем «M2», например «milestone 2», который похож на « R C "(или" CR ") для релизных кандидатов (или кандидатских релизов).


Что касается технической проблемы, вы можете либо не объявлять Objenesis в вашем скрипте Gradle, потому что это необязательный зависимости, тогда тест компилируется и работает нормально, как вы уже заметили. Но предполагая, что вам нужны необязательные зависимости, такие как Objenesis, CGLIB (на самом деле cglib-nodep), Bytebuddy и ASM для других тестов в вашем наборе, вы можете просто сказать Споку не использовать Objenesis в этом случае. Итак, если у вас есть файл сборки Gradle, подобный следующему:

plugins {
  id 'groovy'
  id 'java'
  id 'application'
  id 'org.openjfx.javafxplugin' version '0.0.8'
}

repositories { mavenCentral() }

javafx {
  version = "11.0.2"
  modules = ['javafx.controls', 'javafx.fxml']
}

dependencies {
  implementation 'org.codehaus.groovy:groovy:3.+'
  testImplementation 'org.spockframework:spock-core:2.0-M2-groovy-3.0'

  // Optional Spock dependencies, versions matching the ones listed at
  // https://mvnrepository.com/artifact/org.spockframework/spock-core/2.0-M2-groovy-3.0
  testImplementation 'net.bytebuddy:byte-buddy:1.9.11'
  testImplementation 'org.objenesis:objenesis:3.0.1'
  testImplementation 'cglib:cglib-nodep:3.2.10'
  testImplementation 'org.ow2.asm:asm:7.1'
}

test { useJUnitPlatform() }

application {
  mainClassName = 'de.scrum_master.app.Launcher'
}

installDist {}

Моя версия вашего MCVE будет выглядеть так (извините, я добавил свои собственные имена пакетов, а также импортировал, потому что в противном случае он на самом деле это не MCVE):

package de.scrum_master.app

import javafx.application.Application
import javafx.scene.Scene
import javafx.scene.control.Label
import javafx.scene.layout.StackPane
import javafx.stage.Stage

class App extends Application {
  @Override
  void start(Stage stage) {
    def javaVersion = System.getProperty("java.version")
    def javafxVersion = System.getProperty("javafx.version")
    Label l = new Label("Hello, JavaFX $javafxVersion, running on Java $javaVersion.")
    Scene scene = new Scene(new StackPane(l), 640, 480)
    stage.setScene(scene)
    stage.show()
  }
}
package de.scrum_master.app

import javafx.application.Application

class Launcher {
  static void main(String[] args) {
    Application.launch(App, null)
  }
}
package de.scrum_master.app

import javafx.application.Application
import spock.lang.Specification

class AppSpec extends Specification {
  def 'Launcher.main should call App.launch'() {
    given:
    GroovyMock(Application, global: true, useObjenesis: false)

    when:
    Launcher.main()

    then:
    1 * Application.launch(App, null)
  }
}

Решающей деталью здесь является параметр useObjenesis: false.


Обновление: Просто для справки, это то, как вы бы это делали с классом запуска, реализованным в Java с использованием PowerMockito.

Внимание, для этого решения нужен бегун Sputnik из Spock 1.x, который был удален в 2. Икс. Так что в Spock 2 это в настоящее время не работает, потому что оно основано на JUnit 5 и больше не может использовать @RunWith(PowerMockRunner) и @PowerMockRunnerDelegate(Sputnik), потому что PowerMock в настоящее время не поддерживает JUnit 5. Но я протестировал его со Spock 1.3- groovy -2.5 и Groovy 2.5.8.

package de.scrum_master.app

import javafx.application.Application
import org.junit.runner.RunWith
import org.powermock.core.classloader.annotations.PrepareForTest
import org.powermock.modules.junit4.PowerMockRunner
import org.powermock.modules.junit4.PowerMockRunnerDelegate
import org.spockframework.runtime.Sputnik
import spock.lang.Specification

import static org.mockito.Mockito.*
import static org.powermock.api.mockito.PowerMockito.*

@RunWith(PowerMockRunner)
@PowerMockRunnerDelegate(Sputnik)
@PrepareForTest(Application)
class JavaAppSpec extends Specification {
  def 'JavaLauncher.main should launch JavaApp'() {
    given:
    mockStatic(Application)

    when:
    JavaLauncher.main()

    then:
    verifyStatic(Application, times(1))
    Application.launch(JavaApp)
  }
}
...