[PyCon2016]To mock or not to mock, that is the questions

Post on 16-Apr-2017

528 views 0 download

Transcript of [PyCon2016]To mock or not to mock, that is the questions

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