注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

Koala++'s blog

计算广告学 RTB

 
 
 

日志

 
 

Google Mock进阶篇 [8] (Google Mock Cookbook译文)  

2012-05-04 21:39:44|  分类: C++ |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

Misc Recipes on Using Google Mock

Making the Compilation Faster

无论你相信与否,编译一个Mock类的大部分时间都花费在产生它的构造函数和析构函数上了,因为它们要做很多的任务(比如,对期望的验证)。更严重的是,有不同函数声明的Mock函数,它们的构造函数/析构函数需要由编译器分别产生,所以,如果你Mock许多不同类型的函数,编译你的Mock类会非常慢。

如果你现在发现编译很慢,你可以将Mock类的构造函数/析构函数移出Mock类,将它们放到.cpp文件中。这样做后,即使你在多个文件中#include你的Mock文件,编译器只用产生一次constructordestructor,这样做编译的更快。

让我们以一个例子说明一下,下面是一个原有的Mock类:

// File mock_foo.h.

...

class MockFoo : public Foo {

 public:

  // Since we don't declare the constructor or the destructor,

  // the compiler will generate them in every translation unit

  // where this mock class is used.

 

  MOCK_METHOD0(DoThis, int());

  MOCK_METHOD1(DoThat, bool(const char* str));

  ... more mock methods ...

};

    修改后,变为:

// File mock_foo.h.

...

class MockFoo : public Foo {

 public:

  // The constructor and destructor are declared, but not defined, here.

  MockFoo();

  virtual ~MockFoo();

 

  MOCK_METHOD0(DoThis, int());

  MOCK_METHOD1(DoThat, bool(const char* str));

  ... more mock methods ...

};

    和:

// File mock_foo.cpp.

#include "path/to/mock_foo.h"

 

// The definitions may appear trivial, but the functions actually do a

// lot of things through the constructors/destructors of the member

// variables used to implement the mock methods.

MockFoo::MockFoo() {}

MockFoo::~MockFoo() {}

Forcing a Verification

当你的Mock对象销毁的时候,它会自动检查所有的期望是否满足,如果没满足,会产生一个Google Test失败。这种方式让你可以少去操心一件事。但是如果你不确定你的Mock对象是否会被销毁时,你还是要操心了。

一个Mock对象怎么会最终没有被销毁呢?嗯,它可以是在由被测试的代码在堆上分配的。假设代码中有一个bug,它没能正常地delete Mock对象,你最终可能会在测试中有bug时,让测试通过。

使用一个堆检查工具是一个好主意,可以减少了一些担心,但它的实现不是100%可靠的。所以有时你想在一个Mock对象( 希望如些 ) 销毁前,强制Google Mock去检查它。你可以写:

TEST(MyServerTest, ProcessesRequest) {

  using ::testing::Mock;

 

  MockFoo* const foo = new MockFoo;

  EXPECT_CALL(*foo, ...)...;

  // ... other expectations ...

 

  // server now owns foo.

  MyServer server(foo);

  server.ProcessRequest(...);

 

  // In case that server's destructor will forget to delete foo,

  // this will verify the expectations anyway.

  Mock::VerifyAndClearExpectations(foo);

}  // server is destroyed when it goes out of scope here.

提示:Mock::VerifyAndClearExpectations()函数返回一个bool值来标明检查是否成功(成功为true),所以你可以将函数放到ASSERT_TRUE()中,如果这个断言失败,就没有必要再继续了。

Using Check Points

    有时你也许也在多个检查点重置一个Mock对象:在每个检查点,你可以检查在这个Mock对象上的所有设置的期望是否满足,并且你可以设置在它上面设置一些新的期望,就如同这个Mock对象是新创建的一样。这样做可以让你让你的测试分段地使用Mock对象。

    其中一个使用场景是在你的测试的SetUp()函数中,你也许想借助Mock对象,将你测试的对象放到一个特定的状态。如果在一个合适的状态后,你清除所在Mock对象上的所有期望,这样你可以在TEST_F中设置新的期望。

   正如你也许会发现的一样,Mock::VerifyAndClearExpectations()函数会在这帮助你。或是如果你正用ON_CALL()设置在这个Mock对象上的默认动作,并且想清除这个Mock对象上的默认动作,就用Mock::VerifyAndClear(&mock_object)。这个函数会做如Mock::VerifyAndClearExpectations(&mock_object)相同的工作,并返回相同的bool值,但它还会清除设置在mock_object上设置的ON_CALL()语句。

    另一个可以达到相同效果的技巧是将期望放到序列(sequence)中,在指定的位置将调用放到无效果的(dummy)检查点函数中。然后你可以检查Mock函数在指定时间的行为了。比如,你有下面的代码:

Foo(1);

Foo(2);

Foo(3);

    你想验证Foo(1)Foo(3)都调用了mock.Bar(“a”),但Foo(2)没有调用任何函数,你可以写:

using ::testing::MockFunction;

 

TEST(FooTest, InvokesBarCorrectly) {

  MyMock mock;

  // Class MockFunction<F> has exactly one mock method.  It is named

  // Call() and has type F.

  MockFunction<void(string check_point_name)> check;

  {

    InSequence s;

 

    EXPECT_CALL(mock, Bar("a"));

    EXPECT_CALL(check, Call("1"));

    EXPECT_CALL(check, Call("2"));

    EXPECT_CALL(mock, Bar("a"));

  }

  Foo(1);

  check.Call("1");

  Foo(2);

  check.Call("2");

  Foo(3);

}

    期望指明了第一个Bar(“a”)必须在检查点”1”之前发生,第二个Bar(“a”)必须在检查点”2”之后发生,并且两个检查点之间不应该发生任何事。这种使用检查点的明确写法很容易指明哪个Foo函数调用的Bar(“a”)

Mocking Destructors

有时你想明确一个Mock对象在指定时间销毁,比如,在bar->A()调用之后,但在bar->()调用之前。我们已经知道可以指定Mock函数调用的顺序,所以我们需要做的是MockMock函数的析构函数。

这听起来简单,但一个问题:析构函数是一个特殊的函数,它有着特殊的语法和语义,MOCK_METHOD0对它是无效的:

  MOCK_METHOD0(~MockFoo, void());  // Won't compile!

好消息是你可以用一个简单的模式来达到相同的效果。首先,在你的Mock类中添加一个Mock函数Die(),并在析构函数中调用它,如下:

class MockFoo : public Foo {

  ...

  // Add the following two lines to the mock class.

  MOCK_METHOD0(Die, void());

  virtual ~MockFoo() { Die(); }

};

(如果Die()与已有符号冲突,选另一个名字),现在,我们将测试一个MockFoo对象析构时的问题,转化为测试当Die函数被调用时的问题了:

MockFoo* foo = new MockFoo;

MockBar* bar = new MockBar;

...

{

    InSequence s;

   

    // Expects *foo to die after bar->A() and before bar->B().

    EXPECT_CALL(*bar, A());

    EXPECT_CALL(*foo, Die());

    EXPECT_CALL(*bar, B());

}

Using Google Mock and Threads

重要提示:我们这节所描述的只有Google Mock是线程安全的平台上才是成立的。现在只有支持pthreads库的平台(这包括LinuxMac)才可以。要让它在其它平台上线程安全,我们只需要在”gtest/internal/gtest-port.h”中实现一些同步操作。

在一个单元测试中,如果你可以将一块代码独立出来在单线程环境中测试是最好的。这可以防止竞争和死锁,并使你debug你的测试容易的多。

但许多程序是多线程的,并有时我们需要在多线程环境中测试它,Google Mock也提供了这个功能。

回忆使用一个Mock的步骤:

1.  创建一个Mock对象foo

2.  ON_CALL() EXPECT_CALL()设置它的默认行为和期望。

3.  测试调用foo的函数的代码。

4.  可选的,检查和重置mock

5.  你自己销毁mock,或是让测试代码销毁mock,析构函数会自动检查是否满足。

如果你能遵循下面的简单规则,你的Mock和线程可以幸福地生活在一起:

l  在单线程中执行你的测试代码(相对于被测试代码)。这可以保证你的测试容易被跟踪。

l  显然,你可以在第一步中不用锁。

l  当你做第2步和第5步,要保证没有其它线程访问foo。很显然,不是吗?

l  3步和第4步可以在单线程或是多线程中进行随便你。Google Mock会去处理锁,所以你不需要再做什么事,除非是你测试需要。

如果你违反了这些规则(比如,如果你在其它线程正在访问的一个Mock上测试了期望),你会得到一个不确定的行为。这不会是什么令你开心的事,所以别去尝试。

Google Mock保证一个Mock函数的动作会在调用这个Mock的线程中进行 。比如:

EXPECT_CALL(mock, Foo(1))

  .WillOnce(action1);

EXPECT_CALL(mock, Foo(2))

  .WillOnce(action2);

如果在线程1中被调用Foo(1),且在线程2中调用Foo(2)Google Mock会在线程1中执行action1,在线程2中执行action2

Google Mock不会对多个线程设置动作的顺序( 这样做可能会产生死锁,因为动作可能需要配合 )。这意味着在上面的例子中执行action1action2可能会交错。如果这样不符合你的意思,你应该对action1action2加入同步逻辑,来保证测试线程安全。

同样,要记得DefaultValue<T>是一个全局资源,所以它会影响你程序中的所有活动的Mock对象。很自然的,你不会想在多线程环境中与它纠缠不清,或是你在Mock对象的动作正在进行时,你去使用DefaultValue

Controlling How Much Information Google Mock Prints

Google Mock看到有潜在的错误 ( 比如,一个没有设置期望的Mock函数被调用了 ),它会打印一些警告信息,包括这个函数的参数和返回值,并希望你可以提醒你关注一下,看这里是不是的确是一个错误。

有时你对你的测试正确性比较自信,也许就不喜欢这些友好的提示。有的时候,你在debug你的测试,并在推敲你所测试的代码的行为,那你也许会希望看到每个Mock函数被调用的信息( 包括参数值和返回值 )。很明显,一种打印级别是不是满足所有需求的。

你可以通过—gmock_verbose=LEVEL命令参数来设置打印级别,其中LEVEL是一个有三种取值的字符串。

l  infoGoogle Mock会打印所有的信息,包括正常的消息,警告,错误。在这种级别设置上,Google Mock会记录所有对于ON_CALL/EXPECT_CALL的调用。

l  warningGoogle Mock会打印警告和错误信息,这是默认的。

l  errorGoogle仅会打印错误。

另外,你可以在你的测试中设置打印级别,如:

::testing::FLAGS_gmock_verbose = "error";

现在,请明智地选择打印级别,让Google Mock更好地为你服务。

Running Tests in Emacs

如果你在Emacs中运行你的测试,错误相关的Google MockGoogle Test源文件位置会被高亮标记。只用其中一行上敲回车就会跳到相应的行。或是你可以敲C-x `跳到下一个错误。

为了操作更简单,你可以将加下面几行加入~/.emacs文件中:

(global-set-key "\M-m"   'compile)  ; m is for make

(global-set-key [M-down] 'next-error)

(global-set-key [M-up]   '(lambda () (interactive) (next-error -1)))

然后你可以敲M-m编译,或是用M-up/M-down在错误提示中上下移动。

Fusing Google Mock Source Files

Google Mock的实现包括几十个文件( 包括它自己的测试 )。有时你也许将它们放到几个文件中,这样你就可以更容易地把它们拷贝到一个新机器上。对于这个需求,我们提供了Python脚本fuse_gmock_files.py,它在srcipts/目录下。假设你已经安装了Python 2.4或更高的版本,你进入目录后运行:

Python fuse_gmock_files.py OUTPUT_DIR

你应该会看到OUTPUT_DIR被创建了,并且里面有gtest/gtest.hgmock/gmock.hgmock-gtest-all.cc。这三个文件包含你需要使用Google Mock( Google Test )的所有东西。只需要把它们拷贝到任何地方,然后你就可以开始用Mock写测试了。你可以用sripts/test/Makefile文件作为一个编译你的测试的例子。

  评论这张
 
阅读(4115)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017