[英]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.