Slide 1

Slide 1 text

UNIT TESTING Вадим Пуштаев

Slide 2

Slide 2 text

Принципы Реализация 02/34 Что мы хотим? План доклада

Slide 3

Slide 3 text

03/34 Что мы хотим?

Slide 4

Slide 4 text

Регрессия 04/34

Slide 5

Slide 5 text

Влияние на архитектуру 05/34

Slide 6

Slide 6 text

Понимание 06/34

Slide 7

Slide 7 text

Отладка 07/34

Slide 8

Slide 8 text

Комфорт 08/34

Slide 9

Slide 9 text

Принципы

Slide 10

Slide 10 text

f: X Y Test the interface, not the implemen- tation? X Y 10/34

Slide 11

Slide 11 text

Test the interface, not the implemen- tation? Y f: X Y X 11/34

Slide 12

Slide 12 text

Test first 12/34

Slide 13

Slide 13 text

Unit testing ≠ testing 13/34

Slide 14

Slide 14 text

14/34 unittest xUnit – SUnit – JUnit – RUnit → RSpec – ...

Slide 15

Slide 15 text

unittest Meszaros, Gerard (2007) xUnit Test Patterns 15/34

Slide 16

Slide 16 text

Этапы теста Arrange Act Assert Setup Exercise Verify Teardown 16/34

Slide 17

Slide 17 text

Setup Память Файловая система База данных … 17/34

Slide 18

Slide 18 text

Гибкие параметры def create(dt=None): if dt is None: dt = datetime.now() def download(requests_lib=None): if requests_lib is None: requests_lib = requests class Downloader: def __init__(self, config, logger): self._config = config self._logger = logger 18/34

Slide 19

Slide 19 text

19/34 mock from unittest import mock requests_lib = mock.MagicMock() requests_lib.get.return_value.status_code = 404 @mock.patch('that.module.requests') def test_download(self, requests_lib): requests_lib.get.return_value.status_code = 404

Slide 20

Slide 20 text

20/34 Файловая система import tempfile� with tempfile.TemporaryFile() as f: f.write(b'Test data\n') output = io.StringIO() output.write('Test data\n')

Slide 21

Slide 21 text

База данных Поддельная база 21/34

Slide 22

Slide 22 text

База данных Поддельная база SQLite 22/34

Slide 23

Slide 23 text

База данных Поддельная база SQLite Настоящая база 23/34

Slide 24

Slide 24 text

Данные в базе Копия реальной базы 24/34

Slide 25

Slide 25 text

Данные в базе Копия реальной базы Слепок реальной базы 25/34

Slide 26

Slide 26 text

Данные в базе Копия реальной базы Слепок реальной базы Вручную подготовленные данные 26/34

Slide 27

Slide 27 text

Данные в базе Копия реальной базы Слепок реальной базы Вручную подготовленные данные Пустая база 27/34

Slide 28

Slide 28 text

Данные в базе Копия реальной базы Слепок реальной базы Вручную подготовленные данные Пустая база Фабрики 28/34

Slide 29

Slide 29 text

factory_boy import factory from . import models class UserFactory(factory.django.DjangoModelFactory): class Meta: model = models.User first_name = factory.Sequence( lambda n: 'User #{}'.format(n) ) group = factory.SubFactory(GroupFactory) 29/34 user = UserFactory()

Slide 30

Slide 30 text

Транзакции setUp = BEGIN tearDown = ROLLBACK self.break_db() 30/34

Slide 31

Slide 31 text

def setUp(self): super(BaseTestCase, self).setUp() self._patchers = { 'etcd': patch('common.utils.etcd.Etcd.write'), 'celery': patch('celery.current_app.send_task'), } self._patchers['etcd'].return_value = None self._patchers['celery'].return_value = True for patcher in self._patchers.values(): patcher.start() � Patchers 31/34

Slide 32

Slide 32 text

def assertDirExists(self, dir_path): self.assertTrue(os.path.isdir(dir_path)) def assertNotFileExists(self, file_path): self.assertFalse(os.path.exists(file_path)) def assertFileContent(self, content, path): with open(path, 'rb') as fh: self.assertEqual(content, fh.read()) Custom asserts 32/34

Slide 33

Slide 33 text

@BaseTestCase.cases( ( 'dst', pytz.timezone('Europe/Moscow'), DT.utc(2011, 3, 27, 6, 12, 23), # 10:12:23 (MSD) datetime.date(2011, 3, 27), ), ( 'no_dst', pytz.timezone('Europe/Moscow'), DT.utc(2016, 2, 7, 23, 43, 12), # 02:43:12 (MSK) datetime.date(2016, 2, 8), ), ) def test_date_of_time(self, timezone, dt, date): self.assertEqual(DT.date_of_time(dt, timezone), date) 33/34 Таблицы результатов https://bit.ly/2NvZdUT

Slide 34

Slide 34 text

Итого

Slide 35

Slide 35 text

Спасибо! @pushtaev @pythonetc Пуштаев Вадим

Slide 36

Slide 36 text

Тестирование приватного метода?

Slide 37

Slide 37 text

def count_german_letters(self): return len([ x for x in self._string if self._is_german_letter(x) ]) def delete_german_letters(self): return ''.join( x for x in self._string if not self._is_german_letter(x) ) def _is_german_letter(self, c): return c in string.ascii_lowercase or c in 'ÄäÖöÜü ß'