poniedziałek, 25 lutego 2013

Piotr's Less Obvious Advice on Google Mock: Mocking destructors

Can we verify that a mock object is properly destroyed? Of course! There is a couple of subtle differences between mocking regular functions and mocking destructors. Let's suppose we have a Grinder object that destroys any Piece object passed to its grind method:

void Grinder::grind(Piece * piece) {
  delete piece;
}

Furthermore, Grinder can accumulate a list of Pieces (actually, let it be a list of pointers to Pieces) for destruction that will take place when Grinder itself is destroyed. To add a Piece to the list, we define:

int Grinder::enqueue_piece(Piece * piece) {
  list_of_pieces_.push_back(piece);
  return list_of_pieces_.size();
}

Now, to keep the promise of destroying the queued pieces, Grinder's destructor is defined as follows:

Grinder::~Grinder() {
  for(list<Piece*>::iterator it = list_of_pieces_.begin(); it != list_of_pieces_.end(); it++) {
    delete *it;
  }

}

But how can we mock the destructor of Piece so that we can verify that Grinder really destroys Pieces in both scenarios (on grind method call and on Grinder destruction)? Well, we can't really mock Piece's destructor itself, but we can use a workaround: it's enough to define MockPiece destructor so that it calls another function, such as destroy, that will be used as a signal for us that the destructor has been called.

class Piece {
 public:
  virtual ~Piece() {}
};

class MockPiece : public Piece {
 public:
  MOCK_METHOD0(destroy, void());
  virtual ~MockPiece() { destroy(); }
};

Now, a test like:

TEST(Grinder, CanGrindPiece) {
  MockPiece * piece = new MockPiece;
  Grinder grinder;

  EXPECT_CALL(*piece, destroy());

  grinder.grind(piece);
}

Will pass correctly (we know that grind method deletes the object passed as a pointer argument). But what about this test:

TEST(Grinder, CanGrindWhenDies) {

  MockPiece * p1 = new MockPiece;
  MockPiece * p2 = new MockPiece;
  MockPiece * p3 = new MockPiece;
  list<Piece*> list_of_pieces;
  list_of_pieces.push_back(p1);
  list_of_pieces.push_back(p2);
  list_of_pieces.push_back(p3);

  Grinder grinder;

  EXPECT_CALL(*p1, destroy());
  EXPECT_CALL(*p2, destroy());
  EXPECT_CALL(*p3, destroy());

  grinder.enqueue_piece(p1);
  grinder.enqueue_piece(p2);
  grinder.enqueue_piece(p3);

  //Grinder dies after this line
}

If you try out test example above with the correct implementation of Grinder's destructor, it will pass. But try to comment out the delete statement in Grinder's destructor and see what happens. Google Mock prints error messages like:

.//Grinder_test.cpp:34: ERROR: this mock object (used in test Grinder.CanGrindWhenDies) should be deleted but never is. Its address is @0x809d5e0.

but it still reports that the test itself passed! We would like to have Google Mock report test failure in this case, wouldn't we? The trouble is that Grinder dies when our test is already finished (when it goes out of TEST scope - it is an automatic object). The workaround here is to add another "helper" test that will verify our expectations after  the proper test has been finished. Let's do the following modification: move MockPieces p1, p2 and p3 from CanGrindWhenDies test to global scope. Then, let's add a short helper test:

TEST {
::testing::Mock::VerifyAndClearExpectations(p1);
::testing::Mock::VerifyAndClearExpectations(p1);
::testing::Mock::VerifyAndClearExpectations(p1);
}

That's it: this implementation of expectations and verification will yield correct results ("helper" test passing or failing) depending on whether Grinder's destructor is implemented correctly.

Exercise: try to move VerifyAndClearExpectations statements to CanGrindWhenDies test and see what happens when you run the test with correct and incorrect implementation of Grinder's destructor.

Brak komentarzy:

Prześlij komentarz