是否可以模拟或重载对Boost::Filesystem(.hpp)的直接引用?

Is it possible to mock or overload a direct reference to Boost::Filesystem(.hpp)?

本文关键字:hpp 引用 Filesystem 模拟 重载 是否 Boost      更新时间:2023-10-16

我正试图帮助设计一些围绕Qt c++应用程序控制器的单元测试。

坦率地说,我有两大缺点。首先,我的测试背景很大程度上是基于。net项目的,所以我对c++领域最佳实践的了解最多也就很少。第二,我正在研究的应用程序的设计者并没有考虑到单元测试来构建代码。

具体一点,我正在研究一个包含boost/filesystem/operations.hpp的控制器类。控制器构造函数继续检查目录是否存在,并使用boost文件系统代码中的函数创建目录。

是否有方法重载或模拟此行为?我习惯于在。net中设置IoC容器或至少是依赖注入的构造函数,然后能够在单元测试代码中传递模拟对象。但是,我不确定模板化的头文件如何使用这个概念,或者它是否是c++中的典型实践。

目前,我不能灵活地建议代码更改,因为本周即将发布一个版本。但在此之后,如果有一些简单的代码更改可以提高可测试性,那么这绝对是一个选择。理想情况下,应该有一种在单元测试框架中按原样重载文件系统函数的方法。

我们最终创建了一个通用的文件系统包装器,它调用Boost文件系统并接受它作为类构造函数的参数,这样我们就可以在单元测试时发送模拟版本。

我理解不嘲笑这一点的想法,但我认为快速单元测试对于我们的CI环境来说是有价值的,它可以在签入时运行,也可以在实际触及文件系统的测试中运行。

如果您考虑一下,您需要将一个实例(假设为IFilesystem)注入到您的类中的唯一原因是在测试中模拟它。无论如何,你的非测试代码库的任何部分都不会使用真正的文件系统之外的任何东西,所以可以安全地假设,你可以不是对象,而是类型注入到你的类中,并在你的代码库中自由地使用它们,而不会出现类型冲突。

那么,假设你有兴趣类

struct BuildTree
{
    BuildTree(std::string_view dirname) { /*...*/ }
    bool has_changed()
    {
        // iterates through files in directory
        // recurs into directories
        // checks the modification date against lastModified_
        // uses boost::filesystem::last_write_time(), etc.
    }
private:
    boost::filesystem::file_time_type lastModified_;
};
现在,您可以将类型注入到类中。这个类型将是一个带有一堆静态方法的类。将有一个RealFilesystem类型将重定向到boost::filesystem方法,并且将有一个SpyFilesystem
template <class Filesystem>
struct BuildTree
{
    // ...
    bool has_changed()
    {
        // uses Filesystem::last_write_time(), etc.
    }
private:
    typename Filesystem::file_time_type lastModified_;
};

SpyFilesystem将类似于PIMPL习语,因为静态方法将重定向调用到实际实现。

struct SpyFilesystemImpl;
struct SpyFilesystem
{
    using file_time_type = typename SpyFilesystemImpl::file_time_type;
    static file_time_type last_time_write(std::string_view filename)
    {
        return instance.last_time_write(filename);
    }
    
    // ... more methods
    static SpyFilesystemImpl instance;
};
SpyFilesystemImpl SpyFilesystem::instance{};
// No warranty of completeness provided
struct SpyFilesystemImpl
{
    using file_time_type = std::chrono::system_clock::time_point;
    void create_directory(std::string_view path) { /*...*/ }
    void touch(std::string_view filename)
    {
        ++lastModified_[filename];
    }
    
    file_time_type last_time_write(std::string_view filename)
    {
        return std::chrono::system_clock::time_point{lastModified_[filename]};
    }
private:
    std::unordered_map<std::string, std::chrono::seconds> lastModified_;
};

最后,在每个测试中准备一个SpyFilesystemImpl的实例,将其分配给SpyFilesystem::instance,然后用SpyFilesystem实例化类。就像

// ... our SpyFilesystem and friends ...
// Google Test framework
TEST(BuildTree, PickUpChanges)
{
    SpyFilesystemImpl fs{};
    fs.create_directory("foo");
    fs.touch("foo/bar.txt");
    SpyFilesystem::instance = fs;
    BuildTree<SpyFilesystem> tree("foo");
    EXPECT_FALSE(tree.has_changed());
    SpyFilesystem::instance.touch("foo/bar.txt");
    EXPECT_TRUE(tree.has_changed());
}

这种方法的优点是在生成的二进制文件中没有运行时开销(前提是启用了优化)。但是,它需要更多的样板代码,这可能是一个问题。

我认为boost::filesystem是合理的标准库的扩展。所以你嘲笑它(或不嘲笑)就像你嘲笑std::istream一样。(当然,一般来说,你不会嘲笑它,而是嘲笑你的测试框架提供了必要的环境:您需要的文件用std::istream、目录等读取boost::filesystem)。