• No results found

The Mock Object

In document Test.driven.development.for.Embedded.C (Page 193-197)

While test-driving the LightScheduler, the test fixture intercepted calls to theTimeServiceand theLightControllerto verify the correct behavior of theLightScheduler. The test doubles employed were very simple and were made up of a few static variables along with some getters and setters.

This works fine for those simple interactions. Unfortunately, not all interactions between software entities are so simple. Simple spies or stubs won’t always work. For more complex interactions, we need a different tool, the mock object.

The mock object (or simply the mock) is a test double. It allows a test case to describe the calls expected from one module to another. During test execution the mock checks that all calls happen with the right parameters and in the right order. The mock can also be instructed to return specific values in proper sequence to the code under test. A mock is not a simulator, but it allows a test case to simulate a specific scenario or sequence of events.1

In this chapter, we’ll use a mock to model and confirm the interac-tions between a device driver and the hardware. The mock intercepts commands to and from the device, simulating one usage scenario. In the “Endo-Testing: Unit Testing with Mock Objects” paper, the authors claim that using mock objects means that tests can be written for any-thing. This example shows that even something as hardware-dependent as a device driver can be thoroughly unit tested using a mock.

1. Mock objects were first described in the paper Endo-Testing: Unit Testing with Mock Objects[MFC01].

Download from Wow! eBook <www.wowebook.com>

FLASHDRIVER 194

<<implementation>>

IO

<<implements>>

+ Read(addr) : data + Write(addr, data)

<<interface>

IO

+ Expect_Read(addr, data) + Expect_Write(addr, data)

MockIO

<<hardware>>

+ Program(addr, data) + ProtectBlock(block) + EraseBlock(block) //etc

FlashDriver FlashDriverTest

Figure 10.1: Flash driver and its test fixture

10.1 Flash Driver

When I talk to embedded developers about applying TDD to embedded systems, the statement often comes up, “Yeah, but you can’t test-drive a device driver!” To that I reply, “Yes, you can.” This example will kill two birds with one stone. We’ll get right next to the silicon and develop part of a flash memory driver, and we’ll use a mock object to model and confirm the complex interactions between the driver and the hardware.

For the example, we’ll use the ST Microelectronics 16 Mb flash memory device (M28W160ECT). I chose this one for a number of reasons. The flash memory device requires a specific protocol, involving numerous device reads and device writes. There are also several failure modes, some of which would be very difficult to cause in the actual device. In addition, the device is well documented: its data sheet is fifty pages in length, including numerous flowcharts, tables, and detailed instruc-tions. Finally, the vendor provides a reference design, so we can com-pare implementations.2

2. You can find the device spec and code in the code download for the book in docs/STMicroelectronics.

FLASHDRIVER 195

Figure10.1, on the previous page shows the relationship between the test case, the mock, and the production code. The FlashDriver inter-acts with the hardware through two simple functions: IO_Read( ) and IO_Write( ).

Download src/IO/IO.c

#include "IO.h"

void IO_Write(ioAddress addr, ioData data) {

These two functions are the gateway to the hardware from the driver.

They are statically linked. Because there is no need for the production version of IO_Read( ) and IO_Write( ) during unit testing, a link-time test double works well. Function pointers would give additional flexibility to run unit tests in the target, along with test cases that actually interact with the hardware through the production versions of IO_Read( ) and IO_Write( ). It’s a small modification to convert to function pointers if we change our minds later.

MockIOis standing in for the hardware-dependent implementation ofIO. The test case tells the mock which calls to IO_Read( ) and IO_Write( ) to expect. Then, during the exercise phase, the mock checks each actual call with the expected call. You can think of expectation setting as an additional step in the Four-Phase Test pattern: set up, establish expec-tations, exercise (and check), and check and clean up.

As shown in the flowchart in Figure10.2, on the following page, flash operations can have many interactions. The flowchart shows how to program a specific location in the device. It also identifies the failures that can occur while programming a specific location in the flash.

Programming a memory location is initiated with two device writes.

Then the driver goes into a wait for ready loop, waiting for the device to complete the operation. The flowchart shows that there are four possi-ble outcomes, suggesting the need for at least four test cases. In addi-tion to what the device flowchart suggests, our driver will read back

Report erratum this copy is (P1.0 printing, April, 2011)

Download from Wow! eBook <www.wowebook.com>

FLASHDRIVER 196

b7 == 1 Start

Program Command Write 0x40 to 0x0

Start/Stop Write 0xFF to 0x0

Wait for ready loop

b7 == 1

Figure 10.2: Flash memory program—flowchart

the data from the device to confirm that the write was successful. That adds an additional test case. We’ll also need a test case to simulate a device that never responds. The initial test list is shown in Figure10.3, on the next page.

As shown in the sequence chart in Figure10.4, on the following page, multiple device interactions are needed to program a flash location.

Simple spies and stubs cannot confirm the complex interaction needed.

The mock object is the right tool for this job.

A common misconception is that a mock is a simulator; it is not. A mock is used to simulate and verify a series of interactions for a specific usage scenario. The mock has no idea what a flash device is. Each test case programs the mock for the needed scenario. The mock simulates a single scenario at a time, not the device in total. And it’s good we don’t have to create a flash simulator. Such a simulator would probably be more complicated than the device driver itself.

To be testable, all interactions from the driver to the hardware must go through a pair of functions,IO_Read( ) and IO_Write( ). MockIO inter-cepts calls to those functions. The MockIO versions of IO_Read( ) and IO_Write( ) intercept and check each operation. In addition,IO_Read( ) is

FLASHDRIVER 197

Flash Memory Write - Test List

In document Test.driven.development.for.Embedded.C (Page 193-197)