Spock-mock是Groovy闭包方法吗?

Spock-mock是Groovy闭包方法吗?,groovy,mocking,closures,spock,gdk,Groovy,Mocking,Closures,Spock,Gdk,这就是我想做的: def mockSubdirs = [] mockSubdirs << Mock( File ){ getName() >> 'some subdir' lastModified() >> 2000 } ... File mockParentDir = Mock( File ){ getName() >> 'parent dir' eachDir() >> mockSubdirs.it

这就是我想做的:

def mockSubdirs = []
mockSubdirs << Mock( File ){
    getName() >> 'some subdir'
    lastModified() >> 2000
}
...

File mockParentDir = Mock( File ){
    getName() >> 'parent dir'
    eachDir() >> mockSubdirs.iterator() // ??? NB eachDir is a GDK method
    // I tried things along these lines:
    // listFiles() >> mockSubdirs
    // iterator() >> mockSubdirs.iterator()
}

cut.myDirectory = mockParentDir
。。。所以我的第一个想法是尝试模拟
文件.newDirectoryStream
<代码>文件是
最终版
,因此您必须使用
GroovyMock
,并且由于该方法是
静态的
,因此您似乎必须使用如下内容:

def dirNames = []
myDirectory.eachDir{ 
    dirNames << it.name
}
public static void eachFile(final Path self, final FileType fileType, @ClosureParams(value = SimpleType.class, options = "java.nio.file.Path") final Closure closure) throws IOException {
        //throws FileNotFoundException, IllegalArgumentException {
    checkDir(self);

    // TODO GroovyDoc doesn't parse this file as our java.g doesn't handle this JDK7 syntax
    try (DirectoryStream<Path> stream = Files.newDirectoryStream(self)) {
        for (Path path : stream) {
            if (fileType == FileType.ANY ||
                    (fileType != FileType.FILES && Files.isDirectory(path)) ||
                    (fileType != FileType.DIRECTORIES && Files.isRegularFile(path))) {
                closure.call(path);
            }
        }
    }
}
GroovyMock( Files, global: true )
Files.newDirectoryStream(_) >> Mock( DirectoryStream ){
    iterator() >> mockPaths.iterator()
}
。。。沿着这些路线的尝试似乎不起作用。。。如果有人告诉我Groovy语言机制中的
Files
类不会受到这种模仿的影响,我一点也不会感到惊讶

然后我想大概必须对所讨论的
文件
调用
toPath
,因此尝试了以下方法:

File mockParentDir = Mock( File ){
    toPath() >> {
        println "toPath called"
        Mock( Path )
    }
}
。。。这行没有打印。好吧,我有点困惑:要从
文件中获取
路径
,我给它一个Groovy机制必须使用一些狡猾的东西:可能类似于
getAbsolutePath()
。。。然后从生成的
字符串创建
路径
?这需要对源代码进行更多的检查。。。但是如果是这种情况,那么您就无法强制Groovy使用模拟的
路径

或也许还有其他神秘的Groovy东西在这里发挥作用:元类等等?

您不能用这种方式模拟
每个Groovy
,因为这个方法不属于
文件
类-它是通过类动态添加的。您必须模拟
listFiles()
exists()
isDirectory()
方法,例如:

    File mockParentDir = Mock(File) {
        getName() >> 'parent_dir'
        listFiles() >> mockSubdirs
        exists() >> true
        isDirectory() >> true
    }
mock
exists()
isDirectory()
方法是强制性的,因为如果不指定一个,mock将返回默认值,对于布尔值,默认值为
false
——在这种情况下,您将得到
FileNotFoundException
。如果希望它包含目录,则必须对
mockSubdirs
执行相同的操作

以下是显示正确模拟的示例性测试:

import spock.lang.Specification

class MockDirSpec extends Specification {

    def "test mocked directories"() {
        setup:
        def mockSubdirs = []
        mockSubdirs << Mock( File ){
            getName() >> 'some subdir'
            lastModified() >> 2000
            exists() >> true
            isDirectory() >> true
        }

        File mockParentDir = Mock(File) {
            getName() >> 'parent_dir'
            listFiles() >> mockSubdirs
            exists() >> true
            isDirectory() >> true

        }

        def cut = new ClassUnderTest()
        cut.myDirectory = mockParentDir

        when:
        def names = cut.names()

        then:
        names == ['some subdir']
    }

    static class ClassUnderTest {
        File myDirectory

        List<String> names() {
            def dirNames = []
            myDirectory.eachDir {
                dirNames << it.name
            }
            return dirNames
        }
    }
}
根据
myDirectory
变量所引用的内容生成不同的结果。例如:

def dirNames = []
myDirectory.eachDir{ 
    dirNames << it.name
}
  • 如果
    myDirectory
    指向空目录,
    dirNames
    最终为空
  • 如果
    myDirectory
    指向包含多个文本文件的目录,
    dirNames
    最终为空
  • 如果
    myDirectory
    指向包含2个子目录和10个文本文件的目录,
    dirNames
    最终包含2个元素,即这些子目录的名称
如果我们模拟
eachDir
,因此它总是接受相同的固定输入文件,那么无论是在表示空目录的变量上调用它,还是在包含2个子目录和一些文本文件的目录上调用它,结果都是一样的

在这种情况下,对我来说更有意义的是模拟输入—一个表示为
文件的目录。因此,您可以在不创建真实文件的情况下进行模拟:

  • 空目录
  • 包含单个文本文件的目录
  • 具有单个子目录的目录
  • 包含大量子目录和多个文本文件的目录
  • 等等
您不必模仿
eachDir
方法的行为,这是一个巨大的好处

另一个好处是,你不必更改你的应用程序代码-你仍然可以使用内部的
eachDir
功能。当您模拟输入文件而不是模拟
eachDir
方法时,您只需提供存储在内存中而不是文件系统中的测试数据。想象一下,创建一个所需的文件结构,并使用调试器在运行时研究这些
文件
实例所表示的内容-您可以使用从实际文件系统获取的值重播
文件
类中的所有公共方法返回的内容。这可以让您对存储在文件系统中的特定目录进行一个很好的“内存”模拟。您可以在测试中将其用作输入数据,以模拟运行时发生的情况。这就是为什么我认为嘲讽<代码> EAdidir < /代码>有害-它创建了一个在运行时没有出现的场景。 Bob叔叔也发表了一篇关于嘲弄的博文,总结如下:

“不过,简而言之,我建议你少开玩笑。找到一种方法来测试——设计一种方法来测试——你的代码,这样它就不需要模拟了。保留对建筑重要边界的模拟;然后对它无情。这些都是系统的重要边界,需要对它们进行管理,不仅是为了测试,而且是为了一切。”

资料来源:


这取决于您真正要测试的内容。以下是一个可能有用的示例:

class DirectoryNameHelper {

    /*
     * This is silly, but facilitates answering a question about mocking eachDir
     */
    List<String> getUpperCaseDirectoryNames(File dir) {
        List<String> names = []
        dir.eachDir {File f ->
            names << f.name.toUpperCase()
        }
        names
    }
}

首先,我想对两位和他们各自的答案说声谢谢,他们的答案都很有见解,而且我都为此投了赞成票。我建议OP接受他最喜欢的一个,而不是这一个,因为这里我只是将两种方法统一到一个规范中,使用测试a下的同一个类我还简化了子目录的模拟用法,只使用一个模拟对象,通过
getName()>>>['subDir1','subDir2']
在后续调用中返回两个不同的文件名

因此,现在我们可以更容易地比较两种基本上都是这样做的方法:

  • Szymon的方法是依赖于板上的Spock方法,这是测试Java类时应该使用的方法。Oto,我们正在处理
    eachDir
    ,这是一个特定于Groovy的东西。这里的缺点是,为了完成这种模拟,我们确实需要查看
    eachDir
    的源代码和它的助手之一方法,以找出到底需要做什么,从而使其正确工作。不过,在IMO中,这是一个简单有效的解决方案
  • 杰夫的方法组合
    import groovy.mock.interceptor.MockFor
    import spock.lang.Specification
    
    class EachDirMockSpec extends Specification {
    
        void 'test mocking eachDir'() {
            setup:
            def mockDirectory = new MockFor(File)
            mockDirectory.demand.eachDir { Closure c ->
                    File mockFile = Mock() {
                        getName() >> 'fileOne'
                    }
                    c(mockFile)
    
                    mockFile = Mock() {
                        getName() >> 'fileTwo'
                    }
                    c(mockFile)
            }
    
            when:
            def helper = new DirectoryNameHelper()
            def results
            mockDirectory.use {
                def f = new File('')
                results = helper.getUpperCaseDirectoryNames(f)
            }
    
            then:
            results == ['FILEONE', 'FILETWO']
        }
    }