Slide 1

Slide 1 text

Практическая сторона тестов

Slide 2

Slide 2 text

Александр Винокуров Engineering Manager Компания Samolet

Slide 3

Slide 3 text

Компания Samolet Крупный девелопер и PropTech компания Мы нацелены на автоматизацию и цифровизацию процессов на стройке 50+ внутренних проектов с использованием Django и FastApi Много проектов со сложными бизнес процессами 2

Slide 4

Slide 4 text

Несколько историй из моего опыта

Slide 5

Slide 5 text

История номер раз В нашей экосистеме есть несколько сервисов и нам нужно по неким триггерам синхронизировать данные между ними. Сам процесс написан достаточно сложно и он объективно достаточно не простой. Оставим архитектуру этого решения за скобками — рассмотрим ситуацию AS IS. 4

Slide 6

Slide 6 text

Service A Service B node 1 node 2 sub-node 2.1 sub-node 1.1 sub-node 1.2 sub-node 2.2 sub-node 2.3 node 1 node 3 sub-node 1.3 sub-node 3.1 sub-node 3.2 sub-node 1.2 Синхронизация ~10 000 нод ~3 000 строк кода

Slide 7

Slide 7 text

Продолжение В какой-то момент мы стали замечать, что один из разработчиков тратит примерно 30-50% времени спринта на исправление багов в этой части проекта. Если потрогать любой участок кода из этих примерно 3000 строк, то вероятность того, что создашь баг была крайне велика. Это связано с несколькими факторами: • объективно сложный код • не простые древовидные структуры данных • нулевое покрытие тестами 6

Slide 8

Slide 8 text

Это больно Количество времени на “раскуривание” этой истории, плюс постоянные авралы из-за того, что наша система работает нестабильно и является достаточно хрупкой постоянно порождали у меня и всей команды болевой синдром. Ситуация драматически изменилась, когда мы целенаправленно уделили время написанию тестов на эту части системы. По сути можно было работать в нескольких направлениях — либо понижая сложность системы, либо же увеличивая покрытие тестами. В итоге мы написали примерно 2 000 строк кода тестов и новый код перестал ломать существующий. 7

Slide 9

Slide 9 text

Чему я научился 1 Важно, чтобы на проекте был человек, который знает “как правильно” и способный отстаивать, в том числе и через опыт и цифры, правильные инженерные практики. Например ценность и важность тестов, особенно на сложных или хрупких участках кода. 8

Slide 10

Slide 10 text

Чему я научился 1 Важно, чтобы на проекте был человек, который знает “как правильно” и способный отстаивать, в том числе и через опыт и цифры, правильные инженерные практики. Например ценность и важность тестов, особенно на сложных или хрупких участках кода. 2 Стоимость отсутствия тестов на старте разработки не очень велика, но, чем дальше вы идете, чем сложнее становится бизнес- процесс, тем дороже вам и в плане морали, и в плане денег вам обойдется это приключение. 9

Slide 11

Slide 11 text

Чему я научился 1 Важно, чтобы на проекте был человек, который знает “как правильно” и способный отстаивать, в том числе и через опыт и цифры, правильные инженерные практики. Например ценность и важность тестов, особенно на сложных или хрупких участках кода. 2 Стоимость отсутствия тестов на старте разработки не очень велика, но, чем дальше вы идете, чем сложнее становится бизнес- процесс, тем дороже вам и в плане морали, и в плане денег вам обойдется это приключение. 3 Постоянная спешка и стресс вводит вас в неразрешимую петлю. В какой-то момент нужно сделать стоп и начать разбираться. 10

Slide 12

Slide 12 text

История номер два Важно, чтобы все члены команды понимали и разделяли этот подход. Она является продолжением предыдущей. Мы начали работу над новым сервисом, на момент старта, он был достаточно простым, с понятными бизнес- процессами и кодом. Мы приложили усилия, чтобы он был таким. В какой-то момент я обнаружил что мы не пишем тесты для этого нового сервиса. И, в этом месте, я вспомнил историю номер два — на момент начала всегда кажется, что все просто и прозрачно. Но, чем дальше мы от начала разработки, тем сложнее будет встать на рельсы правильных инженерных практик. 11

Slide 13

Slide 13 text

История номер два Еще, мне кажется, важным налаживать правильную инженерную культуру в команде и делать проекты качественными. Грамотное покрытие тестами, на мой взгляд, значительно улучшает качество проекта. Один из моих любимых аргументов — в какой-то момент я или мы можем уйти из этого проекта, компании, но сам проект останется и, люди, которые придут после нас, будут нам благодарны за качественный проект и высокое покрытие тестами — с таким проектом приятнее и проще работать. 12

Slide 14

Slide 14 text

Практичная теория Давайте поговорим про базовые выгоды от наличия тестов в вашем прекрасном и сияющем проекте

Slide 15

Slide 15 text

Новая логика не ломает предыдущую Этот профит напрямую относится к моей истории номер раз. Потрогав какой-то кусок кода, который был написан, возможно, не вами и, возможно, несколько лет назад вы будете чувствовать себя более уверенно если код был покрыт тестами. Да, у нас есть прекрасные люди под названием тестировщики, которые находят баги в наших приложениях, но, чем больше у нас линий обороны, тем более качественный продукт мы выпускаем и тем больше можем гордиться результатами своей работы. А это важная часть мотивации. 14

Slide 16

Slide 16 text

Взгляд на проект со стороны потребителя Тесты позволяют посмотреть на ваш код с другой стороны — со стороны потребителя, иногда это важно и полезно. Плюс вы всегда сможете открыть тест и посмотреть что ваша, например, ручка (для интеграционных тестов) ожидает на вход и какой будет выход. Чем с большего количества перспектив вы понимаете систему тем лучшим специалистом вы являетесь и тем более комплексные задачи можете решать. 15

Slide 17

Slide 17 text

Качественная и предсказуемая разработка (TLD) С помощью тестов можно быть достаточно уверенным в том, что вы разрабатываете. Задачи могут быть достаточно сложными и много-составными и можно сдать фичу, работающую с одной стороны и не работающую с другой. Как бы мы не работали мы всегда имеем ограничение человеческого мозга — кошелек Миллера, поэтому покрыв какую- то часть нового функционала тестами мы забываем об этом кусочке и можем сосредоточиться на другом. 16

Slide 18

Slide 18 text

Качественная и предсказуемая разработка (TLD) С тестами то же самое — покрыв тестами одну часть, можно забыть о ней — отдать на откуп автоматизации и сосредоточиться на другом кусочке. Это сродни разделению программы на слои — трудно работать со всем и сразу, никто не любит огромных лапшичных кусков кода, которые делают все, намного приятнее работать с кодом, нарезанным слоями, т.к. мы работает только с частью приложения, а не со всем. 17

Slide 19

Slide 19 text

TDL vs TDD При использовании TDL вы пишете маленький участок кода, покрываете его тестами, затем пишете следующий участок кода и опять покрываете его тестами. И так далее, пока не будет полностью реализована фича, полностью покрытая тестами. При использовании TDD вы сначала пишете тест, а затем код, который позволяет проходить тесту. 8

Slide 20

Slide 20 text

Пример TDL Предположим, что вы пишите новую ручку для получения списка товаров. Для начала вам нужно описать базовые бизнес правила в коде, повесить ручку на какой-то URL, позаботиться о сериализации. После написания этих базовых шагов, вы можете написать тест, который: • создает товар в БД • делает запрос на указанный URL • проверяет корректность ответа 19

Slide 21

Slide 21 text

Пример TDL Позже вы добавляете фильтры для вашей ручки списка товаров. Например вам нужно уметь получать товары конкретной категории. Как только вы реализовали эту бизнес логику в коде, вы пишете тест, который: • создает товар категорий А и Б в БД • делает запрос по указанному URL с фильтром по категории А • проверяет что вернулся товар категории А и не вернулся товар категории Б 20

Slide 22

Slide 22 text

Пример TDL Затем вы добавляете возможность поиска по названию товара и пишете тест, который Создает в БД два товара — мишку и зайчика Дергает указанную ручку и просит вернуть только мишку Проверяется что ручка вернула мишку и не вернула зайчика 21

Slide 23

Slide 23 text

Уровни (пирамида) тестирования 22

Slide 24

Slide 24 text

Пример того, как делать не стоит

Slide 25

Slide 25 text

24

Slide 26

Slide 26 text

25

Slide 27

Slide 27 text

26

Slide 28

Slide 28 text

27

Slide 29

Slide 29 text

Хорошие практики — проверяем одну штуку за раз Каждый тест должен проверять одну штуку, если тест проверяет много штук, то такой тест становится сложнее поддерживать. Для интеграционных тестов — один тест дергает одну ручку. Для юнит тестов — один тест проверяет одну функцию или метод. 28

Slide 30

Slide 30 text

Хорошие практики — общие моки Файл настроек tests.py Хорошо, когда есть общие моки для всех внешних сервисов, чтобы не нужно было в каждом тесте помнить о том, что нужно мокать что-то. Если есть какая-то внешняя штука относительного вашего кода — кэш, очередь, внешний сервис — используйте моки 29

Slide 31

Slide 31 text

Хорошие практики — общие моки Общая прослойка, которая, в зависимости от окружения возвращает нужные сущности: 30

Slide 32

Slide 32 text

31

Slide 33

Slide 33 text

Хорошие практики — минималистичные фикстуры Не нужно создавать ненужных сущностей — лучше создать один объект как частный случай создания множества. Это упрощает понимание и поддержание тестов. Помните о правиле — пишем код мы один раз, а читаем множество. Создание лишних сущностей усложняет поддержку кодовой базы в длительной перспективе. Проигрыш начинается и по поддержке тестов, и по времени их исполнения. Огромный тест будет не читаем через пол-года. Так-же как и огромная фикстура, в которую каждый разработчик постепенно складывает все. В итоге мы имеем огромную помойку, которую уже невозможно разгрести из-за большой связанности и проще сделать заново. 32

Slide 34

Slide 34 text

33

Slide 35

Slide 35 text

34

Slide 36

Slide 36 text

Хорошие практики — минималистичные фабрики Если писать фабрики их тоже нужно поддерживать - чтобы под капотом не создавалось ненужных сущностей / связей. Если создавать фабрику, которая под капотом создает другую фабрику и там есть сайд эффект, который создает еще пачку сущностей, то тесты неминуемо начнут проходить все дольше и дольше. Плюс в какой-то момент наш кошелек Миллера переполняется и код превращается в помойку. В рамках нашего проекта мы пришли вот к этим договоренностям: • пишем минималистичные фабрики в том же приложении, где хранятся модели • фабрики хранятся в директории приложения в файле factories.py или директории factories • фабрики минималистичны — по-умолчанию заполняем только обязательные поля. Это нужно для упрощения тестов и ускорения их работы. Все необязательные поля заполняем уже в фикстурах на конкретный тест / группу тестов 35

Slide 37

Slide 37 text

Хорошие практики — покрытие Если в вашем проекте не большое покрытие тестами или оно отсутствует вообще, то хорошо покрыть тестами самые важные бизнес процессы в вашей системе. Например, если у вас интернет магазин, то, кажется важным, покрыть тестами логику добавления товаров в корзину, формирования заказа, получения и подтверждения оплаты, уведомления пользователя. Это нормально — побить все приложение на основные тест кейсы и покрывать их тестами в течении нескольких спринтов. Вы сами в будущем и ваши потомки скажут вам спасибо. 36

Slide 38

Slide 38 text

Хорошие практики — покрытие багов В любой системе случаются баги. В своей практике я пришел к правилу — если прилетает баг на какой-то участок кода, нужно исправить этот участок и написать тест, который бы ломался на не исправленном участке кода (до того, как мы написали фикс) и проходил бы на исправленном (после фикса). Это позволит быть уверенным в том, что текущая правка устраняет баг и в том, что этот баг не воспризведется в будущем. 37

Slide 39

Slide 39 text

Важность юнит тестов Юнит тесты важно писать для сложной бизнес логики. Это облегчит вам жизнь в будущем — эта сложная бизнес логика не будет случайно кем-то сломана, плюс вы сами намного лучше ее поймете, если подергаете функции / методы с разными входными и выходными значениями. Плюс у вас останутся перед глазами примеры входных и выходных значений, в которые можно будет посмотреть в любой момент и понять как работает ваша сложная бизнес-логика. 38

Slide 40

Slide 40 text

Важность юнит тестов — пример из жизни Я работал на проекте, который делал достаточно сложные вычисления временных рядов. Это был проект из мира IOT. У нас было условие, что мы доверяем входным данным, а все “плохие” значения фильтруем на уровне кода. В итоге у нас был ряд очень сложных и функций с неочевидным поведением и я специально покрывал их юнит-тестами. Это позволило: • по входным и выходным параметрам юнит-тестов понимать как именно работает этот сложный код • быть уверенным в том, что код работает верно 39

Slide 41

Slide 41 text

Плохо Хорошо Покрытие основных бизнес-кейсов Фабрики с сайд- эффектами Тесты зависят друг от друга Огромные фикстуры Минималистичные фабрики Общие моки Минималистичные фикстуры Покрытие багов 31

Slide 42

Slide 42 text

@valexjf 41