When you are writing unit tests, you will commonly need to write duplicate implementations of your dependencies to test against systems which do external communication or otherwise manipulate state that you can’t inspect. In other words, test fakes. However, a “test fake” is just one half of the component that you’re building: you’re also generally building a test inspector.
As an example, let’s consider the case of this record-writing interface that we may need to interact with.
1 2 3 4 5 6
class RecordWriter(object): def write_record(self, record): "..." def close(self): "..."
This is a pretty simple interface; it can write out a record, and it can be closed.
Faking it out is similarly easy:
1 2 3 4 5
class FakeRecordWriter(object): def write_record(self, record): pass def close(self): pass
But this fake record writer isn’t very useful. It’s a simple stub; if our application writes any interesting records out, we won’t know about it. If it closes the record writer, we won’t know.
The conventional way to correct this problem, of course, is to start tracking some state, so we can assert about it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
class FakeRecordWriter(object): def __init__(self): self.records =  self.closed = False def write_record(self, record): if self.closed: raise IOError("cannot write; writer is closed") self.records.append(record) def close(self): if self.closed: raise IOError("cannot close; writer is closed") self.closed = True
This is a very common pattern in test code. However, it’s an antipattern.
We have exposed 2 additional, apparently public attributes to application code:
.closed. Our original
RecordWriter interface didn’t have
either of those. Since these attributes are public, someone working on the
application code could easily, inadvertently access them. Although it’s
unlikely that an application author would think that they could read records
from a record writer by accessing
.records, it’s plausible that they might
add a check of
.closed before calling
.close(), to make sure they won’t get
an exception. Such a mistake might happen because their IDE auto-suggested the
completion, for example.
The resolution for this antipattern is to have a separate “fake” object, exposing only the public attributes that are also on the object being faked, and an “inspector” object, which exposes only the functionality useful to the test.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
class WriterState(object): def __init__(self): self.records =  self.closed = False def raise_if_closed(self): if self.closed: raise ValueError("already closed") class _FakeRecordWriter(object): def __init__(self, writer_state): self._state = writer_state def write_record(self, record): self._state.raise_if_closed() self._state.records.append(record) def close(self): self._state.raise_if_closed() self._state.closed = True def create_fake_writer(): state = WriterState() return state, _FakeRecordWriter(state)
In this refactored example, we now have a top-level entry point of
create_fake_writer, which always creates a pair of
RecordWriter. The type of
_FakeRecordWriter can now
be private, because it’s no longer interesting on its own; it exposes nothing
beyond the methods it’s trying to fake.
Whenever you’re writing test fakes, consider writing them like this, to ensure that you can hand application code the application-facing half of your fake, and test code the test-facing half of the fake, and not get them mixed up.