如何使用 Mock() 测试这些文件 i/o 方法?使用 groovy & spock

How do I test test these file i/o methods using Mock()? Using groovy & spock

我在阅读其他 Stack Overflow 帖子时遇到问题,所以几个小时后我正在寻求帮助。

我有两种方法要测试。我想用 Mock 测试第二个,但不知道该怎么做。

这是第一种方法:

String readFileContents(Path filePath) {
        StringBuilder fileContents = new StringBuilder()
        BufferedReader br = Files.newBufferedReader(filePath, StandardCharsets.UTF_8)
        String line
        while ((line = br.readLine()) != null) {
            fileContents.append(line).append('\n')
        }
        fileContents
    }

然后我用

测试了它
class CdmFileSpec extends Specification {

    private CdmFile cdmFile
    private static final String filePath = 'src/test/resources/cdm/test/cdmFileTestFile.txt'

    void setup() {
        cdmFile = new CdmFile()
    }

    void 'test noFileExists'() {
        given:
        Path notRealPath = Paths.get('src/test/resources/cdm//test/notreal.txt')

        when:
        String fileContents = cdmFile.readFileContents(notRealPath)

        then:
        thrown NoSuchFileException
    }

    void 'test readFileContents() reads file contents'() {
        given:
        Path testFilePath = Paths.get(filePath)

        when:
        String fileContents = cdmFile.readFileContents(testFilePath)

        then:
        fileContents.contains('hip hop horrayy\n\nhoooo\n\nheyyy\n\nhoooo')
    }
}

这是有效的,因为我在 filePath 中放置了一个真实的文件。

我想知道...如何使用 Mock 测试下一个方法?

void eachLineInFileAsString(Path filePath,
                                @ClosureParams(value = SimpleType, options = ['java.lang.String'] )Closure applyLine) {
        BufferedReader br = Files.newBufferedReader(filePath)
        String line
        while ((line = br.readLine()) != null) {
            applyLine.call(line)
        }
    }

不需要 Mock,因为您可以使用本地定义的闭包:

def "test the method"() {
    given:
    def result = []
    def closure = { line -> result << line }
    Path testFilePath = Paths.get(filePath)

    when:
    eachLineInFileAsString(testFilePath, closure)

    then: // I'm guessing here
    result == [
        'line 1',
        'line 2',
        'line 3',
        'line 4'
    ]
}

在很多情况下,模拟的问题在于方法会创建自己的依赖项,而不是将它们注入或调用可模拟的服务方法来创建它们。我建议您稍微重构一下代码,将 BufferedReader 创建提取到服务方法中:

package de.scrum_master.Whosebug.q56772468

import groovy.transform.stc.ClosureParams
import groovy.transform.stc.SimpleType

import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Path

class CdmFile {
  String readFileContents(Path filePath) {
    StringBuilder fileContents = new StringBuilder()
    BufferedReader br = createBufferedReader(filePath)
    String line
    while ((line = br.readLine()) != null) {
      fileContents.append(line).append('\n')
    }
    fileContents
  }

  void eachLineInFileAsString(
    Path filePath,
    @ClosureParams(value = SimpleType, options = ['java.lang.String']) Closure applyLine
  ) {
    BufferedReader br = createBufferedReader(filePath)
    String line
    while ((line = br.readLine()) != null) {
      applyLine.call(line)
    }
  }

  protected BufferedReader createBufferedReader(Path filePath) {
    Files.newBufferedReader(filePath, StandardCharsets.UTF_8)
  }
}

现在模拟非常简单,您甚至不再需要测试资源文件(仅当您想在没有模拟的情况下进行集成测试时):

package de.scrum_master.Whosebug.q56772468


import spock.lang.Specification

import java.nio.charset.StandardCharsets
import java.nio.file.NoSuchFileException
import java.nio.file.Path
import java.nio.file.Paths

class CmdFileTest extends Specification {
  private static final String filePath = 'mock/cdmTestFile.txt'
  private static final String fileContent = """
    I heard, that you're settled down
    That you found a girl and you're, married now
    I heard, that your dreams came true
    I guess she gave you things
    I didn't give to you
  """.stripIndent()

  private CdmFile cdmFile

  void setup() {
    cdmFile = Spy() {
      createBufferedReader(Paths.get(filePath)) >> {
        new BufferedReader(
          new InputStreamReader(
            new ByteArrayInputStream(
              fileContent.getBytes(StandardCharsets.UTF_8)
            )
          )
        )
      }
    }
  }

  def "non-existent file leads to exception"() {
    given:
    Path notRealPath = Paths.get('notreal.txt')

    when:
    cdmFile.readFileContents(notRealPath)

    then:
    thrown NoSuchFileException
  }

  def "read file contents into a string"() {
    given:
    Path testFilePath = Paths.get(filePath)

    when:
    String fileContents = cdmFile.readFileContents(testFilePath)

    then:
    fileContents.contains("your dreams came true\nI guess")
  }

  def "handle file content line by line"() {
    given:
    def result = []
    def closure = { line -> result << line }
    Path testFilePath = Paths.get(filePath)

    when:
    cdmFile.eachLineInFileAsString(testFilePath, closure)

    then:
    result == fileContent.split("\n")
  }
}

请注意,我在这里使用的是 Spy(),即保留原始 CdmFile 对象完好无损,仅在使用确切参数 Paths.get(filePath) 调用时存根服务方法 createBufferedReader(..) ].对于其他路径,调用原始方法,这对于 non-existent 文件测试很重要,或者如果您想添加涉及真实资源文件加载的测试,就像在您自己的示例中一样。

每当难以测试 class 或组件、难以注入模拟或以其他方式隔离被测对象时,这就是重构您的应用程序代码以获得更好的可测试性的原因。如果做得好,它还应该导致更好的关注点分离和更好的组件化。如果您的测试变得非常复杂、做作、脆弱并且难以理解和维护,那通常是一种味道,您应该改为重构应用程序代码。