A test double replaces a clock
A test double, also called a fake, is a stand-in that your test hands to the code. It replaces something slow, random or outside your control. With one, a test that depends on the time of day gives the same result at nine in the morning and at midnight.
A greeting that says "Good morning" before noon cannot be tested honestly after noon. A fake clock fixes that.
By the end of this lesson
Note
I can test code that depends on the time or on chance by replacing it with a fixed fake.
Key words
| Word | What it means | Example |
|---|---|---|
| Dependency | something your code needs from outside | the clock |
| Test double | a stand-in used in place of a dependency | |
| Fake | a simple working stand-in with a fixed answer | |
| Mock | a stand-in that also records how it was called | |
| Flaky test | a test that sometimes passes and sometimes fails |
The cells students fill in are left blank in this preview.
Real clocks make flaky tests
Here is a function that greets a user. It asks the real clock for the hour. A test of it passes in the morning and fails in the afternoon, with no change to the code. That is a flaky test, and it teaches a team to ignore red results.
The time is a dependency: something the code needs from outside, and the test cannot control.
Make a folder for this lesson
mkdir -p ~/faketestShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Go into it
cd ~/faketestShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Write a greeting that can take a clock
printf 'import datetime\n\n\ndef real_hour():\n return datetime.datetime.now().hour\n\n\ndef greeting(get_hour=real_hour):\n if get_hour() < 12:\n return "Good morning"\n return "Good afternoon"\n' > greet.pyShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Ask for a greeting with the real clock
python3 -c "from greet import greeting; print(greeting())"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Pass the clock in as a function
The Run printed Good morning or Good afternoon, depending on the hour where you are. The test cannot choose which.
Look at how greeting was written. It takes a function, get_hour, and the real clock is only the default. A test can pass a function of its own that always gives 9, or always 15.
Worked example: four fake hours
| Fake hour | Expected greeting | What the case checks |
|---|---|---|
| 9 | Good morning | An ordinary morning |
| 11 | Good morning | The last hour before noon |
| 12 | Good afternoon | The boundary: noon itself |
| 15 | Good afternoon | An ordinary afternoon |
The cells students fill in are left blank in this preview.
Write tests with fake hours
printf 'import unittest\nfrom greet import greeting\n\n\ndef nine_am():\n return 9\n\n\ndef noon():\n return 12\n\n\ndef three_pm():\n return 15\n\n\nclass GreetingTest(unittest.TestCase):\n def test_morning(self):\n self.assertEqual(greeting(nine_am), "Good morning")\n\n def test_noon_is_afternoon(self):\n self.assertEqual(greeting(noon), "Good afternoon")\n\n def test_afternoon(self):\n self.assertEqual(greeting(three_pm), "Good afternoon")\n' > test_greet.pyShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Run the tests: they pass at any hour
python3 -m unittest test_greetShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Fakes, stubs and mocks compared
People use the words loosely, so here is a simple way to keep them apart.
A fake or stub gives a fixed answer: the nine_am function. A mock also records how it was called, so the test can ask "were you called once, with this value?"
Python's standard library has unittest.mock for this. You do not need to install anything.
Replace the real clock function with a mock
python3 -c "import time; from unittest import mock; time.time = mock.Mock(return_value=1000); print(time.time())"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Chance is a dependency too
A prize draw that picks a random entry gives a different winner on every run. Do the same trick: let the draw take a function, pick_index. In the real program it picks at random. In a test it always picks the first, or the last, so the winner is known.
You test your own rule, and you leave the dice to Python.
A common mistake
Warning
Many people replace so much with fakes that the test checks only the fakes. If the fake and the real thing disagree, the test passes and the program fails. Fake only what is slow, random or outside your control, and keep one integration test with the real thing.