[pycon2016]to mock or not to mock, that is the questions

Post on 16-Apr-2017

531 Views

Category:

Software

0 Downloads

Preview:

Click to see full reader

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