【问题标题】:How to mock standard library functions in D如何在 D 中模拟标准库函数
【发布时间】:2014-10-14 22:17:14
【问题描述】:

我有一个函数,它在文件名上调用 isFile(来自 std.file),然后继续追加 .1、.2、.3 等,检查其中是否存在。

我想对函数进行单元测试,但我需要模拟 isFile。

我环顾四周,找到了模拟类而不是单个函数的方法。

【问题讨论】:

  • 一个选项可能是根据版本(unittest)或其他东西更改导入......所以真正的使用std.file.isFile,但unittest使用your.mocks.isFile
  • 感谢您的想法,但如果我走这条路,我更愿意将使用过的 std 函数包装在某个对象中并使用工厂来决定是实例化真实对象还是模拟对象。我仍在寻找更“外科手术”的解决方案:)
  • 抱歉,本地导入又出了什么问题?比如,导入 std.file;导入std.stdio; int main(string[] args) { writeln(isFile("qq.d"));返回0; } unittest { 导入模拟; writeln(isFile("qq.d")); }
  • 好吧,具体来说,进口必须改变 - 我认为本地进口不会覆盖全球进口,它仍然会要求您消除歧义。所以我在想的是对导入本身进行版本控制,所以实际上只完成了其中一个——那就没有歧义了。不过我得跑一分钟,等我回来再写答案。
  • 实际上,本地导入确实会覆盖全局导入。请参阅dlang.org/module.html 中的“作用域模块”部分 引用:“查找导入以满足该作用域内任何未解析的符号。导入的符号可能会隐藏外部作用域的符号。”

标签: function unit-testing mocking d phobos


【解决方案1】:

由于我的回答和亚当的略有不同,我就加一下,他可以加他的。

您可以为此目的使用“范围导入”。请参阅文档http://dlang.org/module.html

中的相应部分

这也是一个工作示例,如何在单元测试块中模拟 isFile 函数(假设它在模块“模拟”中定义)

import std.file; 
import std.stdio;

int main(string[] args) 
{ 
    writeln(isFile("qq.d")); 
    return 0; 
} 

unittest 
{ 
    import mocks;
    writeln(isFile("qq.d")); 
}

【讨论】:

    【解决方案2】:

    我的简单解决方案是在单独的模块中模拟函数,然后使用version(unittest) 选择您想要的:

    version(unittest)
       import mocks.file;
    else
       import std.file
    
    void main() { isFile("foo"); } // std.file normally, mocks.file in test mode
    

    本地导入 Sergei Nosov 在某些情况下有效,但我认为顶级导入更好,因为通常您希望测试自己的函数:

    string test_me() { isFile("qq.d"); return "do something"; }
    unittest {
        assert(test_me() == "do something");
    }
    

    在这种情况下,作用域导入将不起作用,因为 isFile 使用距离测试太远。但是,在使用点导入时的version(unittest) 可以根据需要重新定义函数。

    也许最好的组合是:

    string test_me() {
        version(unittest) bool isFile(string) { return true; }
        else import std.file : isFile;
        isFile("qq.d"); return "do something";
     }
    

    也就是说,在本地定义假函数……但我也不喜欢那样,现在我想到了,因为函数不一定知道如何测试它。也许导入的 mocks 模块实际上会生成函数指针或可以在 unittest 块中重新分配的东西......嗯,它可能需要一个完整的库,而不仅仅是函数的集合。

    但我认为在我们的两个答案之间,有一个潜在的解决方案。


    我想提的第三件事,虽然有点疯狂,但可以通过使用一些链接器技巧来全局替换另一个模块中的函数:

    import std.file;
    import std.stdio;
    
    // our replacement for isFile...
    pragma(mangle, std.file.isFile.mangleof)
    static bool isFile(string) { return true; }
    
    int main(string[] args)
    {
        writeln(isFile("qq.d")); // always does true
        return 0;
    }
    

    有效的原因是 pragma(mangle) 更改了链接器看到的名称。如果链接器看到两个同名的函数,一个在库中,一个在用户代码中,它允许用户代码替换单个库函数。

    因此,我们使用了我们的函数而不是 lib。重要说明:函数签名必须匹配,否则运行时它会崩溃,它会替换整个程序的函数,而不仅仅是一个位置。不过可以与 version(unittest) 一起使用。

    我不建议实际使用这个技巧,如果你犯了错误很容易随机崩溃,只是想在考虑替换标准库函数时把它扔掉。

    也许这个技巧加上函数指针可用于在运行时替换函数。但主要问题是:由于链接器完全用您的函数替换了库函数,您实际上根本无法使用原始实现!

    您还可以通过编写自己的、给它相同的名称并将其显式传递给编译器来替换整个 std lib 模块。我有时会在 Phobos 上进行开发工作时这样做。但由于这取代了 整个 东西并且是编译器命令行的差异,它可能对单元测试也没有帮助。

    【讨论】:

    • 这是一组有价值的选项。不过,有一条评论。在本地导入模块似乎是一种很好的做法,并且在某种程度上,它被宣传为一种很好的 D 做法。因此,函数 test_me 也可以在本地导入模块,而第二种解决方案将不起作用。在这种情况下,它可能应该提供一个编译时参数或其他东西,如果它愿意考虑测试人员(它应该这样做)。
    • 是的,确实如此。本地进口很棒。
    猜你喜欢
    • 1970-01-01
    • 2011-09-30
    • 2016-11-04
    • 1970-01-01
    • 1970-01-01
    • 2018-09-20
    • 1970-01-01
    • 2011-02-21
    • 2012-02-24
    相关资源
    最近更新 更多