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