Spock 测试绿色,尽管应用程序代码抛出异常

Spock test green although app code threw Exception

我正在使用 Groovy 和 Gradle 进行测试。

我的应用程序代码中有以下几行:

Platform.runLater( new Runnable() {
    void run() {
    ....

        FileChooser fc = getFileChooser()
        File file = fc.showOpenDialog( null )

        // if( file != null && file.exists() ) { <--- this is what I have to put
        if( file.exists() ) { 
            println( "file $file exists")
            analyseFile( file )
        }

如果我模拟(使用 GroovyMock 因为 javafx.stage.FileChooserfinal)所以 fc.showOpenDialog returns null 我会期待 NullPointerException 被扔到 file.exists()... 一个是。

但这并没有出现在测试结果中,所有结果都是绿色的。您发现发生这种情况的唯一方法是查看 class 的测试结果:然后您会看到显示 "StdErr" 的灰色按钮。

这似乎是因为包围 Runnable 是 "swallowing" 它...

Spock 中是否有任何方法可以在 Runnable 中产生异常导致测试失败?

PS 测试代码:

def "getting a null result from the showOpenDialog should be handled OK"(){
    given:
    FileChooser fc = GroovyMock( FileChooser )
    ConsoleHandler ch = Spy( ConsoleHandler ){ getFileChooser() >> fc }
    ch.setMaxLoopCount 10
    systemInMock.provideLines( "o" )
    fc.showOpenDialog( _ ) >> null

    when:
    com.sun.javafx.application.PlatformImpl.startup( {} )
    ch.loop()
    Thread.sleep( 1000L ) // NB I know this is a rubbish way to handle things... but I'm a newb with Spock!

    then:
    0 * ch.analyseFile( _ )
}

这是应用代码的 SSCCE

class ConsoleHandler {
    int loopCount = 0
    def maxLoopCount = Integer.MAX_VALUE - 1
    def response
    FileChooser fileChooser = new FileChooser()

    void analyseFile( File file ) {
        // ...
    }
    void loop() {
        while( ! endConditionMet() ){
            print '> '
            response = System.in.newReader().readLine()
            if( response ) response = response.toLowerCase()
            if( response == 'h' || response == 'help' ){
                println "Help: enter h for this help, q to quit"
            }
            else if ( response == 'o' ){
                Platform.runLater( new Runnable() {
                    void run() {
                        FileChooser fc = getFileChooser()
                        File file = fc.showOpenDialog( null )
                        // if( file != null && file.exists() ) {
                        if( file.exists() ) {
                            analyseFile( file )
                        }
                    }
                })
            }
        }
    }
    boolean endConditionMet() {
        loopCount++
        response == 'q' || loopCount > maxLoopCount
    }
    static void main( args ) {
        com.sun.javafx.application.PlatformImpl.startup( {} )
        new ConsoleHandler().loop()
        com.sun.javafx.application.PlatformImpl.exit()
    }
}

唯一可能需要解释的是 Spock 测试代码中的 systemInMock,它让测试向应用程序代码中的 StdIn 提供文本:

import org.junit.contrib.java.lang.system.TextFromStandardInputStream
import static org.junit.contrib.java.lang.system.TextFromStandardInputStream.emptyStandardInputStream
....
// field of the Test class, of course:
@Rule
public TextFromStandardInputStream systemInMock = emptyStandardInputStream()

我的 Gradle dependencies 子句中对应的行是:

testCompile 'com.github.stefanbirkner:system-rules:1.16.0'

...但我认为这可以通过在 Groovy 测试文件中使用 @Grab 来获得,对吗?

我想我现在意识到这个问题实际上是关于如何最好地在 Spock 中进行 JavaFX 应用程序线程测试...使用 Swing 进行 EDT 测试一直是个问题,毫无疑问,类似的事情也适用。这一切都回到了我身边:当你得到 Platform 到 运行 和 Runnable 时,随着调用者代码继续前进,从 runLater 抛出异常是没有意义的,所以我相信唯一现实的选择是 run() 调用测试代码可以调用的其他方法...

我搜索了 "Spock JavaFX testing",但搜索结果不多...

这是我认为唯一可行的前进方式:像这样改变这一点:

else if ( response == 'o' ){
    Platform.runLater(new Runnable() {
        void run() {
            openFileChooser()
        }
    })
}

ConsoleHandler 的新方法:

def openFileChooser() {
    FileChooser fc = getFileChooser()
    File file = fc.showOpenDialog( null )
    // if( file != null && file.exists() ) {
    if( file.exists() ) {
        analyseFile( file )
    }
}

新的 Spock 测试:

def "getting a null result from the showOpenDialog should be handled OK2"(){
    given:
    FileChooser fc = GroovyMock( FileChooser )
    ConsoleHandler ch = Spy( ConsoleHandler ){ getFileChooser() >> fc }
    fc.showOpenDialog( _ ) >> null

    when:
    com.sun.javafx.application.PlatformImpl.startup( {} )
    ch.openFileChooser()
    Thread.sleep( 1000L )

    then:
    0 * ch.analyseFile( _ )
}

测试失败(万岁!):

java.lang.NullPointerException: Cannot invoke method exists() on null object

但我欢迎更有经验的 Groovy/Spock 提供关于这是否可以改进的意见。

确实我有点疑惑为什么我在when:子句中调用openFileChooser时没有得到"incorrect State"。我认为这是因为在 mock FileChooser 上进行 showOpenDialog 实际上并不涉及任何检查以确保我们当前处于 Java FX 应用程序线程。原来 ...PlatformImpl.startup( {} )Thread.sleep 行可以在该测试中删除。这是理想的做法吗?!嗯...

以后
将此作为唯一答案关闭...实际上,与在 JavaFX-app-thread 中使用普通旧 JUnit 在普通旧 Java 中测试代码一样,同样的考虑适用于这个问题. Spock + Groovy 可能会带来一些问题,或者它们可能会使它变得更容易......我还没有足够的经验来了解它们。