[pycon2016]to mock or not to mock, that is the questions
TRANSCRIPT
To
or not to
that is the question
MO CKMO CK
To
or not to
that is the question
MO CKMO CK
@anabalica
a treatise narrated by
from Potato of Londontowne.ANA BALICA
Thou shalt write tests.
“ ”
William Shakespeare
Chapter one
Mocks simulatethe looks and behaviour
of real objects
REALfig.1 fig.2
MOCK
mocks != stubs
✓ Setup
✓ Test
✓ Verify state
✓ Teardown
✓ Setup
✓ Setup expectations
✓ Test
✓ Verify expectations
✓ Verify state
✓ Teardown
Stubs Mocks
unittest.mock # Python 3.3 mock # Python 2.x
Mock()
Mock>>> from mock import Mock >>> m = Mock() >>> m.foo = 1 >>> m.foo 1 >>> m.bar <Mock name='mock.bar' id='4310136016'>
Mock() MagicMock()
__lt__
__gt__
__len__
__iter__
__bool__
__str__
__int__
__hash__
__exit__
__sizeof__
Mock() MagicMock()
patch()
from mock import patch
with patch('rainbow.Pony') as MockPony: MockPony.return_value = 42
Patching
Patching'rainbow.Pony'
# creatures.py
class Pony: pass
# rainbow.py
from creatures import Pony pony = Pony()
Mock the object where it’s used, not where it came from
Chapter two
Good mocks
with patch.dict('os.environ', {'ANDROID_ARGUMENT': ''}): pf = Platform() self.assertTrue(pf == ‘android')
os.environ
System calls
@mock.patch('sys.stdout', new_callable=six.StringIO) def test_print_live_refs_empty(self, stdout): trackref.print_live_refs() self.assertEqual(stdout.getvalue(), 'Live References\n\n\n')
sys.stdoutStreams
request.urlopen
@patch('django.utils.six.moves.urllib.request.urlopen') def test_oembed_photo_request(self, urlopen): urlopen.return_value = self.dummy_response result = wagtail_oembed("http://www.youtube.com/watch/") self.assertEqual(result['type'], 'photo')
Networking
@patch.object(DataLoader, '_get_file_contents') def test_parse_json_from_file(self, mock_def): mock_def.return_value = ('{"a": 1, "b": 2, "c": 3}', True) output = self._loader.load_from_file('dummy_json.txt') self.assertEqual(output, dict(a=1, b=2, c=3))
json.loadsIO operations
@mock.patch('time.sleep') def test_500_retry(self, sleep_mock): self.set_http_response(status_code=500)
# Create a bucket, a key and a file with self.assertRaises(BotoServerError): k.send_file(fail_file)
Clocks, time, timezonestime.sleep
with mock.patch('random.random', return_value=0.0): with self.assertChanges(get_timeline_size, before=10, after=5): backend.add(timeline, next(self.records))
random.random
Unpredictable results
✓ System calls
✓ Streams
✓ Networking
✓ IO operations
✓ Clocks, time, timezones
✓ Unpredictable results
✓ Save time
✓ Make impossible possible
✓ Exclude external dependencies
Why we like them
Chapter three
mocks Bad
with patch.object(Pony, 'save_base') as mock_save: form = PonyForm(instance=pony, data=data) self.assertTrue(form.is_valid()) saved_pony = form.save() self.assertTrue(saved_pony.age, 3) mock_save.assert_called_once()
Problems?
with patch.object(Pony, 'save_base') as mock_save: form = PonyForm(instance=pony, data=data) self.assertTrue(form.is_valid()) saved_pony = form.save() self.assertTrue(saved_pony.age, 3) mock_save.assert_called_once()
Problems?
with patch.object(Pony, 'save_base') as mock_save: form = PonyForm(instance=pony, data=data) self.assertTrue(form.is_valid()) saved_pony = form.save() self.assertEqual(saved_pony.age, 3) mock_save.assert_called_once()
Problems?
with patch.object(Pony, 'save_base') as mock_save: form = PonyForm(instance=pony, data=data) self.assertTrue(form.is_valid()) saved_pony = form.save() self.assertEqual(saved_pony.age, 3) mock_save.assert_called_once()
Problems?
with patch.object(Pony, 'save_base') as mock_save: form = PonyForm(instance=pony, data=data) self.assertTrue(form.is_valid()) saved_pony = form.save() self.assertEqual(saved_pony.age, 3) mock_save.assert_called_twice()
Problems?
with patch.object(Pony, 'save_base') as mock_save: form = PonyForm(instance=pony, data=data) self.assertTrue(form.is_valid()) saved_pony = form.save() self.assertEqual(saved_pony.age, 3) mock_save.make_me_sandwich()
Problems?
¯\_( )_/¯
Solution 1with patch.object(Pony, 'save_base') as mock_save: form = PonyForm(instance=pony, data=data) self.assertTrue(form.is_valid()) saved_pony = form.save() self.assertEqual(saved_pony.age, 3) mock_save.assert_called_once_with()
Solution 2with patch.object(Pony, 'save_base') as mock_save: form = PonyForm(instance=pony, data=data) self.assertTrue(form.is_valid()) saved_pony = form.save() self.assertEqual(saved_pony.age, 3) self.assertEqual(mock_save.call_count, 1)
Failurewith patch.object(Pony, 'save_base') as mock_save: form = PonyForm(instance=pony, data=data) self.assertTrue(form.is_valid()) saved_pony = form.save() self.assertEqual(saved_pony.age, 3) self.assertEqual(mock_save.sandwich_count, 1)
Solution 3
Test Driven Development
Problems?with patch.object(Pony, 'save_base') as mock_save: form = PonyForm(instance=pony, data=data) self.assertTrue(form.is_valid()) saved_pony = form.save() self.assertEqual(saved_pony.age, 3) self.assertEqual(mock_save.call_count, 1)
Tests pass?
SHIP IT!
Maybe it’s incomple te?
Integration tests
c = Client() response = c.post('/pony/', {'age': 1}) self.assertEqual(response.status_code, 201)
Unit tests
Integration tests
Mocks
Only mock types that you own
Building onThird-Party
Code
Adapter layer
3rd party API
Application objects
Adapter layer
3rd party API
Application objects
Test this
cluster
Mock this
Conclusions
Mocks can be
dangerous
Passing faulty tests give a fal se sense of security
The end