【问题标题】:Can I use a mock File in my Specs2 test for writing to a file? If so, How?我可以在我的 Specs2 测试中使用模拟文件来写入文件吗?如果是这样,如何?
【发布时间】:2013-03-13 07:09:30
【问题描述】:

我已经成功地使用 Specs2 来测试对文件的序列化,但测试使用的是真实文件(写入 /tmp/)。我宁愿不只是为了测试而触摸磁盘。有没有办法使用模拟文件?

def serializeAndDeserializeFromDatafile[X <: CaseClass : Manifest](old: X, maybeGrater: Option[AvroGrater[X]] = None): X = {
val g = maybeGrater.getOrElse(grater[X])

//val outfile = mock[File]
val outfile = new File("/tmp/file1.avro")   

g.serializeToDataFile(outfile, old)  //Serialize to file

val infile = outfile
g.asObjectFromDataFile(infile)       //Deserialize from file 
}

我尝试使用 Mockito 来模拟我的 outfile(上面注释掉的行)。在我天真的尝试中,我可以创建Mock for File, hashCode: 1583021903,但是当我尝试序列化时它似乎是null

我认为我缺少某种“存根”,但我找不到任何足够相似的示例来提出解决方案。任何帮助,将不胜感激。

【问题讨论】:

    标签: scala testing mocking mockito specs2


    【解决方案1】:

    我有一个程序(使用 Akka 自主编写),它广泛处理文件系统操作。我使用 ScalaIO(而不是原生 Java 库 java.io._ 类)编写了它。 ScalaIO 包括 RamFileSystem 等,它允许您以镜像真实文件系统操作的方式模拟文件系统内容和操作,而无需涉及文件系统和 I/O 系统调用。

    【讨论】:

    • 太棒了,谢谢。这是我所希望的,但我很快就跌跌撞撞:val fs = new RamFileSystem(separator="/") println("filesys: " + fs) val path = fs("/tmp/", '/') println("path: " + path) println("isFile "+ path.isFile) val mockedFile = path.createFile() println("mockedFile " + mockedFile) println("isFile " + mockedFile.isFile) val outfile= mockedFile.fileOption println("outfile: " + outfile) 告诉我mockedFile 是一个文件,但outfile: Option[File] 是一个PathType,因此返回None 而不是所需的java.io.File
    • 抱歉格式化。 tl;dr:我是否应该期望能够从这种模拟中获得java.io.File(例如RamPath)?
    • 没有。怎么可能?它必须有一个操作系统级别的文件系统驱动程序才能做到这一点。但 ScalaIO 是用于 I/O 和文件操作的更丰富的 API。
    • 啊,现在我想我明白了。我会使用 ScalaIO 扩展或重写适当的类,然后在这个测试中,我可以简单地将 RamFileSystem 替换为默认类。
    • 没错。到目前为止,我的经验并不是非常广泛,但它运作良好。如果您的 FS 和 IO 交互是可注入或可模拟的,则您可以轻松设计完全不涉及物理文件的测试。
    【解决方案2】:

    您可以尝试使用 OutputStream/InputStream 代替文件吗?

    例子:

    val out:OutputStream = null
    // val testOut  = new ByteArrayOutputStream()
    // val realOut = new FileOutputStream(new File("/tmp/file1.avro"))
    
    g.serializeToOutputStream(out, old)  //Serialize to file
    
    val in:InputStream = null
    // val testIn = new ByteArrayInputStream(testOut.toByteArray)
    // val realIn = new FileInputStream(new File("/tmp/file1.avro"))
    
    g.asObjectFromInputStream(in)       //Deserialize from file
    

    【讨论】:

    • 谢谢,但我不这么认为:底层类只需要File。至少它不是明显您建议的方式如何“模拟”。在使用 Specs2 进行测试之前,我尝试像您建议的那样“模拟”一个流,但在尝试从没有 datafilereader 的二进制文件中读取记录边界时陷入困境。使用较低级别的 Avro 类来编写 avros 看起来/看起来非常不同,而且我已经对这种序列化进行了测试。这里我需要测试这个组件以及它对底层高级datafilewriter/datafilereader 类方法的使用。也许改为模拟这些课程?
    • 嗯,也许我之前的努力不适用于不适合内存的 avros 流,但是对于这个小测试,我会再试一次......仍然很想知道如何但是使用 Mockito!
    【解决方案3】:

    您可以模拟出File,但这并不意味着一切正常。默认情况下,当您在 mock 上调用方法时,它会返回 null(例如,对于 int 值,返回 0)。

    因此,如果您正在测试的函数调用File 方法之一,您将需要提供合理的默认值。例如:

    val f = mock[File]
    
    f.createNewFile returns true
    f.isFile returns true
    f.list returns Array("child1", "child2")
    

    话虽如此,如果您的 grater 对象确实需要一个功能文件来写入,那么真正模拟它可能是不可能的。

    【讨论】:

    • 这是一半没有答案:)。您能否详细说明null 的来源?
    • 这对我已经有帮助了:) 我不是专家,所以我目前唯一可以添加 RE null,是抛出的错误是 Salat-Avro 的序列化异常,在SingleAvroGrater.scala 中(主要处理“索引字段”,但不处理文件创建或设置字段值)。如果我将mock[File] 的默认返回值设置为None,编译器会抱怨它不能转换为java Boolean(我停在那里)。感谢您的示例,我现在看到以这种方式模拟存在很大的困难,并且真实文件听起来更容易接受。感谢 Specs2!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-04
    • 2011-07-01
    • 2019-07-02
    • 2017-10-25
    • 1970-01-01
    相关资源
    最近更新 更多