When to use stubs and mocks?

2019-02-09 12:55发布

问题:

I've this confusion all the time. If I write a code which uses fake code to assert some operation, how do i trust my real implementation when it is started really using the real objects instead of fake ones.

For example, I've this code --

    [Test]
    public void CanCreateContactsWithData()
    {
        using(ISession session = factory.OpenSession())
        using (ITransaction trans = session.BeginTransaction())
        {
            _contactId = (long) session.Save(contact);
            trans.Commit();
        }

        Assert.AreNotEqual(0, _contactId);
    }

This code tests the implementation of a "contact" object whether that gets saved into database or not. If i happened to use a stub instead of a real database connection, do I need to have separate test for storing it in database? And, do you guys call that as integration testing?

Answers are sincerely appreciated.

回答1:

Martin Fowler has a good discussion here.

From his article:

Meszaros uses the term Test Double as the generic term for any kind of pretend object used in place of a real object for testing purposes. The name comes from the notion of a Stunt Double in movies. (One of his aims was to avoid using any name that was already widely used.) Meszaros then defined four particular kinds of double:

  • Dummy objects are passed around but never actually used. Usually they are just used to fill parameter lists.
  • Fake objects actually have working implementations, but usually take some shortcut which makes them not suitable for production (an in memory database is a good example).
  • Stubs provide canned answers to calls made during the test, usually not responding at all to anything outside what's programmed in for the test. Stubs may also record information about calls, such as an email gateway stub that remembers the messages it 'sent', or maybe only how many messages it 'sent'.
  • Mocks are what we are talking about here: objects pre-programmed with expectations which form a specification of the calls they are expected to receive.

Of these kinds of doubles, only mocks insist upon behavior verification.



回答2:

You should be testing the code that you have written. If you wrote the database connection object code, then test it. Otherwise if it is part of a library with it's own tests, you can just mock/stub it and assume that if the connection object passes it's own test suite, then it works.

For example, I wouldn't test calls to Hibernate methods, I assume the Hibernate developers have thoroughly tested that already. But I would test that I was calling the correct method, using a mock to set up that expectation.



回答3:

You use stubs when you just want a function to return some value (or do nothing). You don't really care if the function was called or not, you just want to isolate things.

Mocks are more powerful, as you can also keep track if the function was called, how many times, and even do things with values your function gets.

In your case, if you want to mock the database (so it becomes a unit test rather than a functional one), you can mock ISession and ITransaction. You could then store this values in-memory, and check if the correct values were saved.



回答4:

Yes, using a real database would be more functional or integration testing, depending on your definition. Personally, I feel that unit tests are supposed to test exactly that method only, in isolation of everything else. So regardless of whether the session or the transaction works or not, your unit test must ensure that those objects will be called upon to do their work when and as necessary - that's where mocks and stubs come in. You use them to ensure that your unit test is as decoupled from external functionality so that it can be tested as a basic unit; ideally anyway.



回答5:

Most kinds of unit testing is about testing individual pieces of code, and stubs and mocks are simply tools that assist you in testing piece-by-piece. By testing pieces individually you can probably test each piece in greater detail, but you will not be guaranteed anything about the full picture. Various kinds of integration testing will do that.

When testing bigger pieces of functionality, I often find actual integration tests of database logic to be the least valuable test artifacts, since you will often be testing these same operations at the UI level.