Мы все были в этом: вам дается приложение для обслуживания. Приложение имеет ужасную статистику тестирования, при этом практически не покрывается тестированием. Техническое обслуживание и продление - это большая проблема, и нет реального способа узнать, что вы сломали.

Это невероятно сложная ситуация: инженер до вас решил, что им не нужны модульные тесты и что они были посланы небесами, чтобы быть единственным инженером, который впервые напишет безупречный код.

В унаследованном мной приложении было 0 модульных тестов. Это были интерфейс и бэкэнд на Flutter и Typescript соответственно. Каким-то образом ему удалось сделать свое ядро ​​практически без ошибок: возможно, часы и часы ручного тестирования были потрачены на приложение.

Я решил стать тем изменением, которое хотел увидеть - и, проводя 40 часов в неделю в течение 4 недель, я наконец довел охват до 90%. Вот что я узнал.

1. При написании реализации обязательно подумайте о тестировании.

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

Я был совершенно неправ.

внедрение внедрения зависимостей сделало тестирование во много раз проще. возможность создать именно то взаимодействие, которое вы хотите, - это замечательно и помогает нам создавать надежные, но краткие тесты.

Если вы пишете на компилируемом языке, скорее всего, вы не можете имитировать импорт. С TS / JS вы можете просто имитировать импортированную функцию и вместо этого импортировать свою фиктивную версию - это не совсем работает с Java или Dart, оба скомпилированных языка. вместо этого вам нужно будет внедрить зависимости прямо в объект.

Другие вещи, которые мне пришлось рефакторировать во время тестирования, заключались в извлечении всех строк в константы. Например, если пользователь выходит из системы, мы показываем ему текст «вышел из системы!».

Как мы проверяем, есть ли текст на экране? это могло выглядеть примерно так:

expect(find.text("logged out!"), toFindOneWidget);

но если пользователи UI / UX попросят быстро изменить вышедшую из системы копию, вам придется провести рефакторинг всех тестов, чтобы также использовать новую копию. Вместо этого я извлекаю «вышел из системы!» текст в переменную.

Просто, но очень важно при написании модульных тестов.

2. 100% тестовое покрытие (может быть!) Перебор

Когда я начал, я решил, что могу полностью отправить его и пойти на 100% тестовое покрытие. Я быстро понял, что есть некоторые темные углы, которые было очень сложно или очень бесполезно проверять. Возьмем, к примеру, следующее:

MyListener(
    onEvent: (event) => event.join(
        (state1) => null, 
        (state2) => null, 
        (state3) => doSomething(state3.property)
   )
);

Состояние тестирования 3 совершенно корректно, и это ветвь, которую я в конечном итоге тестирую. но написание теста типа «в состоянии1 / состоянии2 ничего не делать» 1. бесполезно, 2. невозможно.

Подумайте об этом: как вы можете проверить, что функция ничего не делает? Собираетесь ли вы просмотреть каждый макет и убедиться, что он никогда не вызывается? не очень масштабируемый.

Я мог бы написать расширения для всех событий, которые реагируют только на одно состояние, но это работа типа невыполненной работы. Я хочу сосредоточиться на том, чтобы сделать приложение лучше для моих пользователей, а не запускать дофамин в мозгу разработчиков, когда они видят «охват: 100%»!

Однако это не означает, что 100% покрытие плохо для всех приложений. Тестирование - это марафон, а не спринт, но в итоге я пробежал марафон здесь. Я добавил много элементов в очередь, которые я постепенно начну отбирать, когда у меня будет свободное время.

Один из таких пунктов - получить 100% покрытие.

3. Тестировать бэкэнд проще, чем фронтенд.

прежде чем поднимать вилы: это мнение! Если вы не согласны, я хотел бы услышать в комментариях, почему.

Бэкэнд-код очень детерминирован. действия в основном линейны (т. е. маршрут вызывает службу), и каждая точка входа очень четкая, с одинаково четкими выходами.

Фронтенд не такой.

Один экран в приложении имеет не только одну точку входа: возможно, push-уведомление deeplink отправит вас на экран. Возможно, некоторые поля предварительно заполнены, или пользователь не является администратором, поэтому его на самом деле даже не должно быть на этом экране.

Вы можете написать код внешнего интерфейса, который легко протестировать, но это еще один случай: «Пожалуйста, помните о тестировании при написании!». Каждое состояние загрузки, состояние сбоя, должно иметь свои собственные модульные тесты, и вы можете знать лишь очень много о том, какие сбои могут возникнуть, когда пользователь использует приложение.

Пример в моем приложении, который я не думал тестировать, но принес ошибки, - когда пользователь спамил две кнопки вперед и назад, они создали условие гонки. это состояние гонки будет означать, что событие загрузки будет отправлено после события загрузки, и все приложение будет зависать на экране загрузки навсегда.

Откуда мне было знать, чтобы проверить это взаимодействие? В моем бэкэнде я знаю, что не собираюсь вызывать функцию в цикле, достаточную для того, чтобы могло произойти подобное поведение.

в общем, люди - существа непредсказуемые, а машины невероятно предсказуемые. Тестировать поведение машины намного проще, чем тестировать людей.

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

Не позволяйте этому отвлекать вас от написания тестов. Каждый раз, когда я добавляю новую функцию, я словно глоток свежего воздуха, зная, что все остальное работает, как задумано.

Модульное тестирование - одно из моих любимых занятий как разработчика, так как я чувствую себя невероятно продуктивным и доволен своим кодом. Я больше не пишу код без тестов, держу микросценарии, и я для них гораздо более эффективный разработчик.

Есть ли у вас какие-нибудь истории тестирования? Есть вопросы или комментарии? Пожалуйста, дайте мне знать в комментариях! Я всегда читаю и учусь.

Спасибо за прочтение!