在 Scala 中,单元测试如何创建临时目录以用作测试的一部分?
我正在尝试对依赖于目录的类进行单元测试
class UsesDirectory(directory : java.io.File) {
...
}
我正在寻找以下形式的东西:
class UsesDirectorySpec extends FlatSpec {
val tempDir = ??? //missing piece
val usesDirectory = UsesDirectory(tempDir)
"UsesDirectory" should {
...
}
}
此外,任何关于在单元测试完成后适当清理资源的意见/建议都会有所帮助。
提前感谢您的考虑和回复。
Krzysztof 的回答提供了一个很好的策略,可以避免在测试中完全需要临时目录。
但是,如果您确实需要
UsesDirectory
来处理真实文件,您可以执行以下操作来创建一个临时目录:
import java.nio.file.Files
val tempDir = Files.createTempDirectory("some-prefix").toFile
关于清理,您可以使用 JVM 关闭挂钩机制来删除您的临时文件。
(
java.io.File
确实提供了 deleteOnExit()
方法,但它不适用于非空目录)
您可以使用
sys.addShutdownHook {}
实现自定义关闭挂钩,并使用 Files.walk
或 Files.walkTree
删除临时目录的内容。
您可能还想看看更好的文件库,它为常见的文件操作提供了一个不太冗长的 scala API,包括
File.newTemporaryDirectory()
和 file.walk()
File
在Java中测试起来非常麻烦。没有简单的方法来创建某种虚拟文件系统抽象,可用于测试。
一个很酷的解决方法是创建某种包装器,可用于存根和模拟。
例如:
trait FileOps { //trait which works as proxy for file
def getName(): String
def exists(): Boolean
}
object FileOps {
class FileOpsImpl(file: File) extends FileOps {
override def getName(): String = file.getName //delegate all methods you need
override def exists(): Boolean = file.exists()
}
implicit class FromFile(file: File) { //implicit method to convert File to FileOps
def toFileOps: FileOpsImpl = new FileOpsImpl(file)
}
}
然后你必须在课堂上使用它而不是
File
:
class UsesDirectory(directory : FileOps) {
...
}
//maybe you can even create implicit conversion, but it's better to do it explicitly
val directory = new UserDirectory(file.toFileOps)
那有什么好处?
在您的测试中,您可以提供
FileOps
的自定义实现:
class UsesDirectorySpec extends FlatSpec {
val dummyFileOps = new FileOps {
override def getName(): String = "mock"
override def exists(): Boolean = true
}
//OR
val mockFileOps = mock[FileOps] //you can mock it easily since it's only trait
val usesDirectory = UsesDirectory(dummyFileOps)
"UsesDirectory" should {
...
}
}
如果您使用这种或类似的方法,您甚至不需要在单元测试中接触文件系统。
我遇到了类似的要求,要针对示例文件对我的功能进行单元测试。我使用
scalatest.BeforeAndAfterAll
包中的 scalatest
特性解决了它。
import com.nag.SensorApp
import com.nag.repository.SensorStats
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.{BeforeAndAfterAll}
import java.io.File
import java.io.FileWriter
import java.nio.file.{Files, Path, Paths}
import scala.collection.mutable
class AggregatorServiceTests extends AnyFlatSpec with BeforeAndAfterAll {
val testFileName = "test-files"
val configMap = mutable.Map.empty[String, File]
接下来我们覆盖
beforeAll()
方法,它可以在测试前设置tempFile。相应的afterAll()
将确保在执行测试后清理这些临时文件。
override def beforeAll() = {
val tempDir = Files.createTempDirectory(testFileName)
print("The temporary directory = " +(tempDir))
val tempFile = new File(osAwareFilename(tempDir, testFileName))
tempFile.createNewFile()
configMap.put(testFileName, tempFile)
val myWriter = new FileWriter(tempFile)
myWriter.write("sensor-id,humidity\ns1,10\ns2,88\ns1,NaN")
myWriter.close()
}
override def afterAll(): Unit = {
super.afterAll()
val tempFile = configMap.get(testFileName).get
tempFile.delete()
}
private def osAwareFilename(inputPath : Path, filename : String) : String = {
if (System.getProperty("os.name").contains("Windows")) {
inputPath.toFile.getPath + "\\" + filename
}
else {
inputPath.toFile.getPath + "/" + filename
}
}
完成此设置后,可以从
tempFile
访问 configMap
并在我们的测试中使用。
"The Aggregator Service" should "sum sensor s1 and s2 records ignoring NaN " in {
val fileForTest = configMap.get(testFileName).get
println("File for test :: "+ fileForTest.toPath.getParent)
val aggregatorService = SensorApp.main(fileForTest.toPath.getParent)
assert(SensorStats.totalRowsProcessed == 3)
assert(SensorStats.totalFailedMeasurements == 1)
assert(SensorStats.invalidSensors.keys.size == 0)
assert(SensorStats.sensorInfo.get("s1").get.sum == 10)
}
}