Unit testing 具有文件系统依赖项的单元测试代码

Unit testing 具有文件系统依赖项的单元测试代码,unit-testing,dependency-injection,dependencies,Unit Testing,Dependency Injection,Dependencies,我正在编写一个组件,给定一个ZIP文件,该组件需要: 解压缩文件 在解压缩的文件中查找特定的dll 通过反射加载该dll并对其调用方法 我想对这个组件进行单元测试 我很想编写直接处理文件系统的代码: void DoIt() { Zip.Unzip(zipfile,“C:\\foo\\Unzip”); System.IO.File myDll=File.Open(“C:\\foo\\unzip\\SuperSecret.bar”); myDll.InvokeSomeSpecialMethod();

我正在编写一个组件,给定一个ZIP文件,该组件需要:

  • 解压缩文件
  • 在解压缩的文件中查找特定的dll
  • 通过反射加载该dll并对其调用方法
  • 我想对这个组件进行单元测试

    我很想编写直接处理文件系统的代码:

    void DoIt()
    {
    Zip.Unzip(zipfile,“C:\\foo\\Unzip”);
    System.IO.File myDll=File.Open(“C:\\foo\\unzip\\SuperSecret.bar”);
    myDll.InvokeSomeSpecialMethod();
    }
    
    但是人们经常说,“不要编写依赖于文件系统、数据库、网络等的单元测试。”

    如果我以单元测试友好的方式编写,我想它会是这样的:

    void DoIt(IZipper zipper、IFileSystem文件系统、IDllRunner runner)
    {
    字符串路径=zipper.Unzip(zipfile);
    IFakeFile file=fileSystem.Open(路径);
    runner.Run(文件);
    }
    
    耶!现在它是可测试的;我可以将测试加倍(mock)输入DoIt方法。但代价是什么?我现在必须定义3个新接口,以使其可测试。我到底在测试什么?我正在测试DoIt函数是否与其依赖项正确交互。它不测试zip文件是否正确解压缩,等等

    感觉我不再测试功能了。感觉上我只是在测试课堂互动

    我的问题是:对依赖于文件系统的东西进行单元测试的正确方法是什么


    编辑我使用的是.NET,但是这个概念也可以应用Java或本机代码。

    一种方法是编写unzip方法来获取InputStreams。然后,单元测试可以使用ByteArrayInputStream从字节数组构造这样的InputStream。该字节数组的内容在单元测试代码中可能是一个常量。

    假设“文件系统交互”在框架本身中得到了很好的测试,创建处理流的方法,并进行测试。打开FileStream并将其传递给方法可能不在您的测试范围内,因为FileStream.Open经过了框架创建者的良好测试。

    我不愿意使用仅为方便单元测试而存在的类型和概念来污染我的代码。当然,如果它能让设计更干净、更好,那么就很棒了,但我认为情况往往并非如此

    我对这一点的看法是,您的单元测试将尽可能多地进行测试,这可能不是100%的覆盖率。事实上,可能只有10%。关键是,单元测试应该是快速的,并且没有外部依赖关系。他们可能会测试类似“当您为该参数传入null时,此方法会引发ArgumentNullException”的情况

    然后,我将添加集成测试(也是自动化的,可能使用相同的单元测试框架),这些测试可以具有外部依赖性,并测试端到端场景,例如


    在度量代码覆盖率时,我同时度量单元测试和集成测试。

    您不应该测试类交互和函数调用。相反,你应该考虑集成测试。测试所需的结果,而不是文件加载操作。

    命中文件系统没有错,只考虑它是一个集成测试,而不是单元测试。我将硬编码的路径与相对路径交换,并创建一个TestData子文件夹来包含单元测试的zip

    如果您的集成测试运行时间太长,那么将它们分开,这样它们就不会像您的快速单元测试那样频繁地运行


    我同意,有时我认为基于交互的测试可能会导致太多的耦合,并且常常无法提供足够的价值。您确实希望在这里测试解压文件,而不仅仅是验证您调用了正确的方法。

    这似乎更像是一个集成测试,因为您取决于理论上可能发生变化的特定细节(文件系统)


    我将把处理操作系统的代码抽象到它自己的模块(类、程序集、jar等等)中。在您的情况下,如果找到了特定的DLL,您希望加载该DLL,因此请创建IDLLOADER接口和DllLoader类。让您的应用程序使用该接口从DllLoader获取DLL并测试。。你不必为解压代码负责,好吗?

    这真的没什么错,只是你称之为单元测试还是集成测试的问题。您只需确保,如果确实与文件系统交互,不会产生意外的副作用。具体地说,请确保在您自己删除创建的任何临时文件后进行清理,并且不要意外地覆盖与您正在使用的临时文件具有相同文件名的现有文件。始终使用相对路径,而不是绝对路径


    在运行测试之前,最好将
    chdir()
    放入一个临时目录中,然后再将
    chdir()
    返回。正如其他人所说,第一个可以作为集成测试。第二个测试只测试函数实际应该做什么,这是单元测试应该做的


    如图所示,第二个示例看起来有点毫无意义,但它确实让您有机会测试函数如何响应任何步骤中的错误。在示例中没有任何错误检查,但在实际系统中可能有,依赖项注入可以让您测试对任何错误的所有响应。那么成本是值得的。

    对于单元测试,我建议您在项目中包括测试文件(EAR文件或等效文件),然后在单元测试中使用相对路径,即“./testdata/testfile”

    只要项目正确导出/导入,单元测试就应该工作

    耶!现在它是可测试的;我可以将测试加倍(mock)输入DoIt方法。但代价是什么?我现在必须定义3个新接口,以使其可测试。我到底在测试什么?我在测试我的DoIt功能