Тестировать… не так уж и сложно

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

В последние годы появилась тенденция использовать некоторые варианты классных методов, таких как TDD и BDD, и они хорошо применяются; но большинство разработчиков не следуют правилам или считают, что сроки не позволяют им просто писать модульные или интеграционные тесты. Боже… Я даже слышал, как некоторые люди говорили: «[sic] Письменный тест предназначен для команд контроля качества, верно?»

Что ж… это моя попытка показать, что тестирование не настолько сложно и может стать легкой частью вашего ежедневного рабочего процесса.

Примечания:

  • Я буду ссылаться на «функция», когда ссылаюсь на методы или функции нечетко, такое обсуждение «правильного имени» не имеет отношения к Эта тема
  • Большая часть моей работы связана с Android, поэтому, вероятно, это будут примеры только на Kotlin/Java, но большинство концепций применимы и к другим языкам, например, private в JavaScript могут быть функциями, не представленными в module.exports

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

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

  • «Я собираюсь использовать алгоритм X для сортировки этих элементов»
  • «Мы должны добавить для него функцию хеш-кода»
  • «Этот шаблон проектирования решит все наши проблемы»
  • «Просто используйте этот инструмент для решения этой маленькой проблемы»

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

Тестирование может помочь нам во многих отношениях. С точки зрения поклонников TDD мы можем сказать, что «тестирование помогает нам ничего не ломать», что означает, что если у нас есть тесты, новые функции/изменения не нарушат текущий код, и с точки зрения TDD это верно. Но не все ежедневно следуют TDD, поэтому нам нужна другая точка зрения. Для обычного разработчика тестирование — это способ «проверить, работает ли эта штука». Это означает, что мы можем подтвердить, что даже когда в код поступают новые вещи (функции или изменения), код по-прежнему пригоден для использования и не будет генерировать что-то неожиданное или что-то странное, чего не должно было произойти.

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

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

Когда мы должны проверить

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

Писать тесты для вещей, которые вы уже считаете само собой разумеющимися, — плохая идея. Одним из примеров этого является написание теста для GSON (известная библиотека Java от Google для анализа JSON) и проверка того, успешно ли он анализирует JSON. Это плохая идея, потому что Google пишет эту библиотеку, очевидно, они уже написали несколько тестов для основных функций библиотеки, поэтому вы просто тратите время на написание теста для хорошо зарекомендовавшей себя библиотеки. Вместо этого вы, вероятно, захотите написать тест для некоторого класса/функции, который использует GSON, а это совершенно другой случай, и он является допустимым.

Я задаю себе эти вопросы, когда хочу написать тест:

Имею ли я контроль над этим кодом и могу ли я свободно его изменять?

Этот код могут видеть и использовать другие?

Если ответ положительный на оба вопроса, то вам, вероятно, нужны тесты для этого фрагмента кода.

Когда прекращать тестирование

Есть случаи, которые не должны быть частью теста. Одним из примеров ООП является наличие класса, который представляет только данные, т. е. в стране Java это будет POJO (обычный старый объект Java), а в стране Kotlin это будет класс данных, который имеет геттеры и сеттеры. Написание теста для геттера или сеттера бесполезно и не должно быть тестом. Тесты должны касаться не самих данных, а того, «что произойдет с этими данными», а геттеры/сеттеры непосредственно касаются того, что происходит с данными.

Другие случаи, которые применяются для не требуются тесты: частные функции. Это действительно обсуждается и на таких сайтах, как StackOverflow. Мое личное мнение таково: если у вас есть очень сложная частная функция и вы чувствуете, что вам нужны тесты для нее, вам, вероятно, нужно разделить эту функцию на более мелкие приватные функции, а затем проверьте, действительно ли они нуждаются в приватности и вообще нуждаются ли они в тестировании. Это часто происходит в мире ООП и даже в мире сценариев, где разработчики сталкиваются с необходимостью тестирования, но начинают усложнять структурирование своих тестов. Существуют способы (например, отражение в Java или @VisibleForTesting в Android), чтобы сделать вещи, которые должны быть приватными, видимыми в тестах. Тем не менее… Я предлагаю избегать такого рода тестирования, которое включает в себя уловки и читы, чтобы получить что-то, предположительно недостижимое более простыми способами.

Как вы должны тестировать

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

Есть еще вещи, которые вы должны иметь в виду при выполнении тестов:

  • Изолированная среда. Я не хочу говорить о сложных вещах (например, о контейнерах или виртуальных средах, как в python). Под «изолированной средой» я подразумеваю способ избежать взаимодействия вашего кода с внешними источниками. Как настоящие базы данных или настоящие веб-сервисы. Все еще есть части тестирования, которые включают в себя общение с этими внешними источниками, но даже в этом случае большая часть среды должна быть локализована (или контролироваться, как бы вы это ни называли).
  • Подумайте о худшем случае. Да, это основная причина, по которой мы начинаем писать тесты. Что произойдет, если я получу незарегистрированные данные для своего коммутатора? Что происходит с пустыми списками или пустыми массивами? Что произойдет, если пользователь введет смайлик? Что происходит, когда пользователь не говорит по-английски и пишет неанглийский символ? Какой лимит на текст? Что произойдет, если сервер изображений не работает? и т. д. Вы всегда должны сначала думать о плохих сценариях и писать для них тесты. Вы будете удивлены результатами написания таких тестов
  • Подумайте о хороших случаях. После того, как вы исправите все проблемы с плохими сценариями, ваш код все еще должен работать правильно, а если нет, вам следует снова исправить код, чтобы удовлетворить плохие и хорошие сценарии. Помните: Надейтесь на лучшее, готовьтесь к худшему.
  • Подумайте, как другие будут использовать ваш код. При написании тестов подумайте о том, как другие будут вызывать ваши функции, какие данные они будут использовать для этого вызова и чего они ожидают в результате. Старайтесь писать свои тесты с учетом этого, как реальных вызовов вашего кода и того, что может произойти, если кто-то отправит неправильные параметры вашим функциям. Подумайте об этом сценарии с двух точек зрения: другие разработчики и конечные пользователи. Всегда ли конечный пользователь будет вводить свой пароль с первого взгляда? Поймет ли разработчик сначала тот сложный объект Response, который вы возвращаете, когда он вызывает вашу функцию getProfile()? Является ли Response подходящим названием для этого класса? это только примеры того, с чем вы можете столкнуться при написании тестов, пытаясь выяснить, все ли работает так, как ожидалось, или вам нужно будет что-то изменить.
  • Продумайте сценарии. Создайте весь сценарий пользователя и проверьте, какие части вашего кода задействованы, затем напишите этот сценарий в качестве теста и убедитесь, что все работает так, как указано в сценарии.
  • Не бойтесь что-то менять. При написании тестов вы заметите, что, вероятно, вызов функции не так прост, и вам нужно много данных для выполнения этого вызова. Что ж, напишите тест в стиле счастливого пути, как вы ожидаете, а затем проведите рефакторинг кода, чтобы он работал так, как говорит тест. В большинстве случаев вам придется адаптировать код для прохождения теста, а не наоборот. Если вы адаптируете тест так, чтобы он проходил без изменения кода, вероятно (скорее всего), вы пишете ложноположительный результат.
  • Не переусердствуйте со своими тестами. Есть время для лучших практик и сложного кода. Написание тестов — не тот момент. Тест должен быть прямолинейным в отношении их намерений. Он должен быть удобочитаемым, чтобы любой человек без контекста тестируемого кода мог понять цель указанного теста.

Вывод

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

Еще многое можно сказать о тестировании, и я буду писать об этом в будущих статьях и постараюсь правильно обновить эту, добавив ссылки на другие статьи и примеры.

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

(Еще раз спасибо Alejandro Tellez и Jhoon Saravia за помощь с правильной грамматикой и опечатками)