簡體   English   中英

Objenesis 依賴導致實例化錯誤

[英]Objenesis dependency causes instantiation error

剛剛開始一個新的 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'

(注意,我假設這些版本適用於我現在正在使用的 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,OS Linux Mint 18.3。

構建.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中。

我們不是必須使用全局 GroovyMock 嗎?

如果你想檢查交互,是的。 但實際上您正在測試 JavaFX 啟動器而不是您的應用程序。 所以我懷疑有什么好處。 我會專注於測試App類。 還想象一下,您將使用 Java 而不是 Groovy 編寫具有 main 方法的類。 從 Java 代碼調用時,Groovy 模擬將不起作用,尤其是全局模擬。 然后你最終會通過 Spock 的 Powermockito 進行測試,這也可以工作,但你仍然會測試 JavaFX 啟動器而不是你的應用程序。

也不是稍微極端地說任何使用Groovy嘲笑是錯誤的?

我沒那么說過。 我說:“你的應用程序設計可能有問題”。 我這么說的原因是因為使用 Groovy 模擬和模擬靜態方法之類的東西是測試代碼的味道。 您可以檢查氣味,然后確定它可以,而 IMO 在大多數情況下不是。 此外,問題也可能出在測試本身,而不是應用程序設計,在這種情況下我會說它是。 但這是有爭議的,因此我將在下面進一步向您提供解決方案。

在這種情況下,從技術上講,如果您確實堅持測試 JavaFX 啟動器,則全局Application模擬是您唯一的方法,因為即使是App上的全局模擬也不起作用,因為啟動器使用反射來調用App構造函數,並且不會被攔截模擬框架。

你說 Spock spock-core:2.0-M2-groovy-3.0 是一個“預發布版”。 我在此頁面上看不到任何內容(...)。 你怎么知道的?

您已經通過查看 GitHub 存儲庫發現了這一點,但我只是在包含“M2”的不尋常版本號中看到它,例如“里程碑 2”,類似於發布候選(或候選版本)的“RC”(或“CR”)發布)。


至於技術問題,您可以不在 Gradle 腳本中聲明 Objenesis,因為它是一個可選的依賴項,然后測試編譯並運行良好,正如您自己已經注意到的那樣。 但是假設您需要可選的依賴項,例如 Objenesis、CGLIB(實際上是 cglib-nodep)、Bytebuddy 和 ASM 以用於套件中的其他測試,您可以告訴 Spock 在這種情況下不要使用 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參數。


更新:僅供參考,這是使用 PowerMockito 在 Java 中實現的啟動器類的方法。

注意,這個解決方案需要 Spock 1.x 中的Sputnik runner,它在 2.x 中被刪除。 因此,在 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)
  }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM