使用单元测试对一个带有“traits”模板参数的对象进行测试

3
我有一个使用 Visual Studio 2008 C++03 的项目,我想对其中使用特性模板参数(策略设计模式)暴露静态方法的类进行单元测试。我正在使用 Google Test 和 Google Mock 框架。
例如:
/// the class under test
template< typename FooTraits >
class Foo
{
public:
    void DoSomething()
    {
        FooTraits::handle_type h = FooTraits::Open( "Foo" );
        /* ... */
        FooTraits::Close( h );
    };
};

/// a typical traits structure
struct SomeTraits
{
    typedef HANDLE handle_type;
    static handle_type Open( const char* name ) { /* ... */ };
    static void Close( handle_type h ) { /* ... */ };
};

/// mocked traits that I would like to use for testing
struct MockTraits
{
    typedef int handle_type;
    static MOCK_METHOD1( Open, handle_type( const char* ) );
    static MOCK_METHOD1( Close, void( handle_type ) );
};

/// the test function
TEST( FooTest, VerifyDoSomethingWorks )
{
    Foo< MockTraits > foo_under_test;

    // expect MockTraits::Open is called once
    // expect MockTraits::Close is called once with the parameter returned from Open
    foo_under_test.DoSomething();
};

显然,这个方法不能直接使用。Google Mock无法模拟静态方法,我需要在测试中创建一个Mocked类的实例来设置其行为和期望。
那么,使用Google Test / Google Mock正确地对接受模板策略的类进行单元测试的方法是什么?

这有点儿天才。创建一个被模拟的单例类,然后让存根引用它。你能把这放在答案里吗?我想点击那个小绿勾勾。 - PaulH
1个回答

3
你能创建一个带有非静态方法的类,并创建一个全局实例(或在你的特性中静态化),并使你的特性类推迟到它吗?
因此,为了澄清Rob评论所启发的想法:
struct FunnyDoodad
{
   FunnyDoodad();
   ~FunnyDoodad();

   MOCK_METHOD1( Open, HANDLE( const char* ) );
   MOCK_METHOD1( Close, void( handle_type ) );

};

struct FunnyGlobal {
  FunnyGlobal() : pimpl() {}
  ~FunnyGlobal() { delete pimpl; }

  // You'd want some protection here rather than just dereferencing.
  // it's the whole point.  I'd suggest using whatever unit test assertion
  // mechanism your framework uses and make it a fatal check.    
  handle_type Open(char const* name) { return pimpl->Open(name); }
  void Close(handle_type h) { pimpl->Close(h); }
private:
   FunnyDoodad * pimpl;

  friend struct FunnyDoodad;

  void register(FunnyDoodad* p) { pimpl = p; }
  void deregister() { pimpl = 0; }
};

FunnyGlobal funnyGlobal;

FunnyDoodad::FunnyDoodad() { funnyGlobal.register(this); }
FunnyDoodad::~FunnyDoodad() { funnyGlobal.deregister(); }

struct TestTraits
{
    typedef HANDLE handle_type;
    static handle_type Open( const char* name ) { return funnyGlobal.Open(name); };
    static void Close( handle_type h ) { funnyGlobal.Close(h); };
};

TEST_CASE(blah)
{
   FunnyDoodad testDoodad;

   ...
}

我想上述内容可以模板化,甚至可以成为一种模式...也许。

1
你可能不想将模拟实例设为静态或全局的。这样一来,一个测试的操作可能会干扰后续测试的处理。相反,可以有一个全局或静态的指针指向特定实例。在每个测试中声明一个本地模拟特性类,并将全局指向它。 - Rob Kennedy
@RobKennedy - 我不确定。我能理解你的想法,但是取消引用指针会导致未定义行为。测试中的错误可能会导致各种问题...包括通过(如果你特别不幸,我可以看到它发生的方式)。因此,我建议使用比指针更智能的东西。一个静态对象,其工作是引用一个测试本地对象,该对象将使用类似RAII的概念向此对象注册/注销。这可能是绝对最安全的方法,可以确保每个测试都被重置。 - Edward Strange

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接