如何使用Mock()测试这些文件I / O方法?使用Groovy和Spock

时间:2019-06-26 12:08:42

标签: unit-testing groovy file-io 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)
        }
    }

2 个答案:

答案 0 :(得分:1)

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

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'
    ]
}

答案 1 :(得分:1)

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

package de.scrum_master.stackoverflow.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.stackoverflow.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对象不变,并且仅在使用参数createBufferedReader(..)进行调用时才对服务方法Paths.get(filePath)进行存根处理。对于其他路径,将调用原始方法,这对于不存在的文件测试或在您自己的示例中想要添加涉及实际资源文件加载的测试非常重要。

每当难以测试类或组件,难以注入模拟或以其他方式隔离要测试的主题时,这就是重构应用程序代码以实现更好的可测试性的原因。如果做得正确,它还应该导致更好的关注点分离和更好的组件化。如果您的测试变得非常复杂,人为设计,脆弱且难以理解和维护,那么通常就是一种气味,您应该重构应用程序代码。