[读书笔记]Test Strategy - Intruductions of Stub and Mock

来源:互联网 发布:淘宝店铺中奖怎么查询 编辑:程序博客网 时间:2024/04/24 22:04

Stubs are a mechanism for faking the behavior of real code that may exist or that
may not have been written yet.
Stubs allow you to test a portion of a system without
the other part being available. They usually do not change the code you’re
testing but instead adapt to provide seamless integration.

DEFINITION stubA stub is a portion of code that is inserted at runtime in place of

the real code, in order to isolate calling code from the real implementation.

The intent is to replace a complex behavior with a simpler one

that allows independent testing of some portion of the real code.

 

 

Here are some examples of when you might use stubs:

Ø         When you cannot modify an existing system because it is too complex and
fragile

Ø         For coarse-grained testing, such as integration testing between different
subsystems

 

Stubs usually provide very good confidence in the system being tested. With stubs,
you are not modifying the objects under test, and what you are testing is the same
as what will execute in production. Tests involving stubs are usually executed in
their running environment, providing additional confidence.


On the downside, stubs are usually hard to write, especially when the system to
fake is complex. The stub needs to
implement the same logic as the code it is
replacing
, and that is difficult to get right for complex logic. This issue often leads
to having to debug the stubs! Here are some cons of stubbing:

Ø         Stubs are often complex to write and need debugging themselves.

Ø         Stubs can be difficult to maintain because they’re complex.

Ø         A stub does not lend itself well to fine-grained unit testing.

Ø         Each situation requires a different strategy.


In general, stubs are better adapted for replacing coarse-grained portions of code.
You would usually use stubs to replace a full-blown external system like a filesystem,
a connection to a server, a database, and so forth. Using stubs to replace a
method call to a single class can be done, but it is more difficult. (We will demonstrate
how to do this with mock objects in chapter 7.) 

 

 

 

Mock

Unit-testing each method in isolation from the other methods or the environment
is certainly a nice goal. But how do you perform this feat? You saw in chapter 6
how the stubbing technique lets you unit-test portions of code by
isolating them
from the environment
(for example, by stubbing a web server, the filesystem, a
database, and so on). But what about fine-grained isolation like being able to
isolate
a method call to another class
? Is that possible? Can you achieve this without
deploying huge amounts of energy that would negate the benefits of having tests?
The answer is, “Yes! It is possible.” The technique is called mock objects. Tim
Mackinnon, Steve Freeman, and Philip Craig first presented the mock objects
concept at XP2000. The mock-objects strategy allows you to unit-test at the finest
possible level and develop method by method, while providing you with unit tests
for each method.

 

Testing in isolation offers strong benefits, such as the ability to test code that has

not yet been written (as long as you at least have an interface to work with). In

addition, testing in isolation helps teams unit-test one part of the code without

waiting for all the other parts.

But perhaps the biggest advantage is the ability to write focused tests that test

only a single method, without side effects resulting from other objects being

called from the method under test. Small is beautiful. Writing small, focused tests

is a tremendous help; small tests are easy to understand and do not break when

other parts of the code are changed. Remember that one of the benefits of having

a suite of unit tests is the courage it gives you to refactor mercilessly—the unit tests

act as a safeguard against regression. If you have large tests and your refactoring

introduces a bug, several tests will fail; that result will tell you that there is a bug

somewhere, but you won’t know where. With fine-grained tests, potentially fewer

tests will be affected, and they will provide precise messages that pinpoint the

exact cause of the breakage.

Mock objects (or mocks for short) are perfectly suited for testing a portion of

code logic in isolation from the rest of the code. Mocks replace the objects with

which your methods under test collaborate, thus offering a layer of isolation. In

that sense, they are similar to stubs. However, this is where the similarity ends,

because mocks do not implement any logic: They are empty shells that provide

methods to let the tests control the behavior of all the business methods of the

faked class.

 

DEFINITION mock objectA mock object (or mock for short) is an object created to

stand in for an object that your code will be collaborating with. Your

code can call methods on the mock object, which will deliver results

as set up by your tests.

 

In your tests, you have sometimes used the real objects and sometimes

mocked them.

Here are some cases in which mocks provide useful advantages over the real

objects. (This list can be found on the C2 Wiki at http://c2.com/cgi/wiki?Mock-

Object.) This should help you decide when to use a mock:

Ø         Real object has non-deterministic behavior

Ø         Real object is difficult to set up

Ø         Real object has behavior that is hard to cause (such as a network error)

Ø         Real object is slow

Ø         Real object has (or is) a UI

Ø         Test needs to query the object, but the queries are not available in the real

object (for example, “was this callback called?”)

Ø         Real object does not yet exist

 

JUnit best practices: don’t write business logic in mock objects

The single most important point to consider when writing a mock is that it

should not have any business logic. It must be a dumb object that only does

what the test tells it to do. In other words, it is purely driven by the tests. This

characteristic is exactly the opposite of stubs, which contain all the logic (see

chapter 6).

There are two nice corollaries. First, mock objects can be easily generated, as

you will see in following chapters. Second, because mock objects are empty

shells, they are too simple to break and do not need testing themselves.

 

JUnit best practices: only test what can possibly break

You may have noticed that you did not mock the Account class. The reason is

that this data-access object class does not need to be mocked—it does not depend

on the environment, and it’s very simple. Your other tests use the Account

object, so they test it indirectly. If it failed to operate correctly, the tests

that rely on Account would fail and alert you to the problem.

 

Design patterns in action: Inversion of Control (IOC)

Applying the IOC pattern to a class means removing the creation of all object

instances for which this class is not directly responsible and passing any needed

instances instead. The instances may be passed using a specific constructor,

using a setter, or as parameters of the methods needing them. It becomes

the responsibility of the calling code to correctly set these domain objects on

the called class.

 

Using mocks as Trojan horses

DEFINITION expectation—When we’re talking about mock objects, an expectation is a

feature built into the mock that verifies whether the external class calling

this mock has the correct behavior. For example, a database connection

mock could verify that the close method on the connection is

called exactly once during any test that involves code using this mock.