Spying to Sense Using a Mock

One of the goals of writeSnippet is to send the total seconds to the descriptor. In the prior section, we inspected this value by turning it into a member variable. We can instead have the WavReader use a test double of the descriptor that captures the total seconds sent to it.

You learned about how to create test doubles using Google Mock in Chapter 5, Test Doubles. Since we’re using CppUTest for our current example, we’ll use its own mock tool, CppUMock. As with Google Mock, we define a derivative of WavDescriptor that will spy on messages sent to its add function.

wav/15/WavReaderTest.cpp
 
class​ MockWavDescriptor : ​public​ WavDescriptor {
 
public​:
 
MockWavDescriptor(): WavDescriptor(​""​) {}
 
void​ add(
 
const​ ​string​&, ​const​ ​string​&,
 
uint32_t totalSeconds,
 
uint32_t, uint32_t) override {
*
mock().actualCall(​"add"​)
*
.withParameter(​"totalSeconds"​, (​int​)totalSeconds);
 
}
 
};

The override to add requires us to make it virtual in WavDescriptor.

wav/15/WavDescriptor.h
*
virtual​ ​void​ add(
*
const​ std::​string​& dir, ​const​ std::​string​& filename,
*
uint32_t totalSeconds, uint32_t samplesPerSecond,
*
uint32_t channels) {
*
// ...
 
WavDescriptorRecord rec;
 
cpy(rec.filename, filename.c_str());
 
rec.seconds = totalSeconds;
 
rec.samplesPerSecond = samplesPerSecond;
 
rec.channels = channels;
 
 
outstr->write(​reinterpret_cast​<​char​*>(&rec), ​sizeof​(WavDescriptorRecord));
 
}

The highlighted line in MockWavDescriptor tells a global CppUTest MockSupport object (retrieved by a call to mock) to record an actual call to a function named “add.” The MockSupport object also captures the value of a parameter named “totalSeconds.” (I quote these names since you get to choose them arbitrarily when you work with CppUMock. It’s a cheap form of reflection.)

We inject the test double into the WavReader by passing it as a third argument to its constructor.

wav/15/WavReaderTest.cpp
 
TEST_GROUP(WavReader_WriteSnippet) {
*
shared_ptr<MockWavDescriptor> descriptor{​new​ MockWavDescriptor};
*
WavReader reader{​""​, ​""​, descriptor};
 
istringstream input{​""​};
 
FormatSubchunk formatSubchunk;
 
ostringstream output;
 
DataChunk dataChunk;
 
char​* data;
 
uint32_t TwoBytesWorthOfBits{2 * 8};
 
void​ setup() override {
 
data = ​new​ ​char​[4];
 
}
 
 
void​ teardown() override {
 
mock().clear();
 
delete​[] data;
 
}
 
};

In the test itself, we tell the MockSupport object to expect that a function with name add gets called. We tell it to expect that the call is made with a specific value for its parameter named totalSeconds. This arrangement of the test is known as setting an expectation. Once the actual call to writeSnippet gets made, the Assert portion of the test verifies that all expectations added to the MockSupport object were met.

wav/15/WavReaderTest.cpp
 
TEST(WavReader_WriteSnippet, UpdatesTotalSeconds) {
 
dataChunk.length = 8;
 
formatSubchunk.bitsPerSample = TwoBytesWorthOfBits;
 
formatSubchunk.samplesPerSecond = 1;
*
mock().expectOneCall(​"add"​).withParameter(​"totalSeconds"​, 8 / 2 / 1);
 
reader.writeSnippet(​"any"​, input, output, formatSubchunk, dataChunk, data);
*
mock().checkExpectations();
 
}

We correspondingly change the descriptor pointer member to be a shared pointer. Using a shared pointer allows both the test and the production code to properly manage creating and deleting the descriptor object. We also choose to default the descriptor argument to be a null pointer in order to minimize the impact to existing tests.

wav/15/WavReader.h
 
class​ WavReader {
 
public​:
 
WavReader(
 
const​ std::​string​& source,
 
const​ std::​string​& dest,
 
std::shared_ptr<WavDescriptor> descriptor=0);
 
// ...
 
private​:
 
// ...
 
std::shared_ptr<WavDescriptor> descriptor_;
 
};
wav/15/WavReader.cpp
 
WavReader::WavReader(
 
const​ std::​string​& source,
 
const​ std::​string​& dest,
*
shared_ptr<WavDescriptor> descriptor)
 
: source_(source)
 
, dest_(dest)
*
, descriptor_(descriptor) {
*
if​ (!descriptor_)
*
descriptor_ = make_shared<WavDescriptor>(dest);
 
 
channel = DEF_CHANNEL(​"info/wav"​, Log_Debug);
 
log.subscribeTo((RLogNode*)RLOG_CHANNEL(​"info/wav"​));
 
 
rLog(channel, ​"reading from %s writing to %s"​, source.c_str(), dest.c_str());
 
}
 
 
WavReader::~WavReader() {
*
descriptor_.reset();
 
delete​ channel;
 
}

We don’t have to change a lick of code in the writeSnippet function that we’re testing! Code in writeSnippet blissfully continues to call the add function on the descriptor without knowing whether the descriptor is a production WavDescriptor instance or a test double.

We can finally complete the story by test-driving that writeSnippet obtains and passes on the file size. In fact, we choose to co-opt the test UpdatesTotalSeconds and update it to verify both arguments. We create a mock for FileUtil in order to support answering a stub value given a request for a file size. We inject the FileUtil mock instance of FileUtil using a setter function instead of the constructor.

wav/16/WavReaderTest.cpp
 
class​ MockWavDescriptor : ​public​ WavDescriptor {
 
public​:
 
MockWavDescriptor(): WavDescriptor(​""​) {}
 
void​ add(
 
const​ ​string​&, ​const​ ​string​&,
 
uint32_t totalSeconds,
 
uint32_t, uint32_t,
 
uint32_t fileSize) override {
 
mock().actualCall(​"add"​)
 
.withParameter(​"totalSeconds"​, (​int​)totalSeconds)
*
.withParameter(​"fileSize"​, (​int​)fileSize);
 
}
 
};
 
*
class​ MockFileUtil: ​public​ FileUtil {
*
public​:
*
streamsize size(​const​ ​string​& name) override {
*
return​ mock().actualCall(​"size"​).returnValue().getIntValue();
*
}
*
};
 
 
TEST_GROUP(WavReader_WriteSnippet) {
 
shared_ptr<MockWavDescriptor> descriptor{​new​ MockWavDescriptor};
 
WavReader reader{​""​, ​""​, descriptor};
 
*
shared_ptr<MockFileUtil> fileUtil{make_shared<MockFileUtil>()};
 
 
istringstream input{​""​};
 
FormatSubchunk formatSubchunk;
 
ostringstream output;
 
DataChunk dataChunk;
 
char​* data;
 
uint32_t TwoBytesWorthOfBits{2 * 8};
 
 
const​ ​int​ ArbitraryFileSize{5};
 
 
void​ setup() override {
 
data = ​new​ ​char​[4];
*
reader.useFileUtil(fileUtil);
 
}
 
 
void​ teardown() override {
 
mock().clear();
 
delete​[] data;
 
}
 
};
 
 
TEST(WavReader_WriteSnippet, SendsFileLengthAndTotalSecondsToDescriptor) {
 
dataChunk.length = 8;
 
formatSubchunk.bitsPerSample = TwoBytesWorthOfBits;
 
formatSubchunk.samplesPerSecond = 1;
 
*
mock().expectOneCall(​"size"​).andReturnValue(ArbitraryFileSize);
 
 
mock().expectOneCall(​"add"​)
 
.withParameter(​"totalSeconds"​, 8 / 2 / 1)
 
*
.withParameter(​"fileSize"​, ArbitraryFileSize);
 
 
reader.writeSnippet(​"any"​, input, output, formatSubchunk, dataChunk, data);
 
 
mock().checkExpectations();
 
}
wav/16/WavReader.cpp
 
void​ WavReader::writeSnippet(
 
const​ ​string​& name, istream& file, ostream& out,
 
FormatSubchunk& formatSubchunk,
 
DataChunk& dataChunk,
 
char​* data
 
) {
 
// ...
 
writeSamples(&out, data, startingSample, samplesToWrite, bytesPerSample);
 
 
rLog(channel, ​"completed writing %s"​, name.c_str());
 
*
auto​ fileSize = fileUtil_->size(name);
 
 
descriptor_->add(dest_, name,
 
totalSeconds, formatSubchunk.samplesPerSecond, formatSubchunk.channels,
*
fileSize);
 
 
//out.close(); // ostreams are RAII
 
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.223.190