[pycon2016]to mock or not to mock, that is the questions
Post on 16-Apr-2017
531 Views
Preview:
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
top related