我应该将成员函数虚拟化以使类可测试吗?

时间:2012-09-27 05:50:50

标签: c++ unit-testing mocking virtual

我正在开发一个类,其简化版本如下:

class Http_server {
public:
    void start(int port)
    {
        start_server();
        std::string content_type = extract_content_type(get_request());
    }

private:
    void start_server()
    {
        ...
    }

    std::string get_request()
    {
        ...
    }

    std::string extract_content_type(const std::string& request) const
    {
        ...
    }
};

现在我想为extract_content_type编写一个测试用例。问题是:它是私有的,所以我不能从外面调用它。我可以测试的唯一功能是start,但实际启动服务器(start_server)并等待请求(get_request)。

我怎么看,我有三个选择:

  1. extract_content_type公开
  2. extract_content_type解压缩到实用程序类或命名空间
  3. 使start_serverget_request虚拟并创建一个覆盖它们的模拟对象
  4. 我不想公开任何内容或移动到只在一个类中使用过一次的实用程序命名空间,所以最不邪恶的是选项3。

    我在V8代码库中至少看到过一个这样的例子: http://code.google.com/p/v8/source/browse/trunk/test/cctest/test-date.cc

    不过,我不确定这是不是一个好主意。 virtual不是C ++中的默认值,原因有两个:

    1. 这会导致性能/内存开销(尽管在我的情况下可能无关紧要)
    2. 并非每个类都应该用作基类,使其明确也是一个设计决策
    3. 你会做什么?与无用的虚拟生活在一起?或者根本不测试功能?我不是进入TDD,也不想成为TDD,但是在测试中开发像extract_content_type这样的函数会更容易。

6 个答案:

答案 0 :(得分:3)

我想你可能有另一种选择:

让单元测试课成为您班级的朋友进行测试

class Foo {
  public:
#ifdef UNITTEST
    friend class FooTest;
#endif
    ...

  protected:
    ...

  private:
    ...
};

以下是参考:http://praveen.kumar.in/2008/01/02/how-to-unit-test-c-private-and-protected-member-functions/

答案 1 :(得分:3)

答案是您不测试私人功能。理想情况下,你甚至不写它们,你通过重构创建它们(虽然我承认这在实践中非常困难)。

在测试公共/受保护功能时,应隐式测试您的私有函数。如果私有函数的功能没有完全断言,则表示该函数执行在类之外没有可见效果的事情。

这不仅仅是TDD问题。由于私有函数是一个实现细节,我通常认为我可以重构它们而不会破坏任何东西。如果对一个函数进行了测试,并且我决定重构它的签名,那就不再存在了,让我非常困惑。

答案 2 :(得分:1)

我可以推荐一些API,它允许您使用私有方法。它被称为Typemock Isolator ++。作为一个例子,我创建了一个测试,它改变你的extract_content_type方法行为,调用它(尽管它是私有的),然后声明:

TEST_METHOD(TestExtractContentType)
    {   
        Http_server* server = new Http_server();

        std::string res ("result");
        PRIVATE_WHEN_CALLED(server, extract_content_type, NULL).Return(&res);

        std::string result;
        ISOLATOR_INVOKE_MEMBER(result, server, extract_content_type, NULL);

        PRIVATE_ASSERT_WAS_CALLED(server, extract_content_type);
        Assert::AreEqual(string("result"), result);
    }

无需更改代码。我刚刚为编译器添加了ISOLATOR_TESTABLE标记。

ISOLATOR_TESTABLE std::string extract_content_type(const std::string& request) const 

您可以阅读更多here。在单元测试中处理非公开成员时非常方便。

答案 3 :(得分:0)

如果extract_content_type不需要Http_server类中包含的任何信息,则它不必属于该类。实际上,看起来你需要一个可以返回自己内容类型的请求本身的类。然后可以测试该请求类。

答案 4 :(得分:0)

我可以提出一个不同的选择,但我不确定你是否愿意。

您可以创建

#define TESTING_VIRTUAL

将扩展为virtual或根据编译时选项扩展为任何内容。因此,如果您正在编译测试,则可以将其设置为替换为virtual,如果是用于生产,则它不是虚拟方法。

如果宏扩展到privatepublic,取决于您是否在测试模式下进行编译,也可以这样做。

答案 5 :(得分:0)

我同意Björn对此的看法。无论一个班级拥有或没有的私人功能都取决于班级,并且对于呼叫者而言没有任何保留。如果您删除了这个私有方法会发生什么,那么您认为提取内容类型并不难,因此您可以直接在start函数中执行此操作?好吧,你会破坏测试用例,尽管类正在按预期工作。私人是私人的! :)

我的建议是,您将extract_content_type放在实用程序类中以进行内容处理,并在测试中使用该类。那时不需要服务器代码来测试这个类。