Меня шокирует, как мало разработчиков знают о всестороннем тестировании.

Я постоянно получаю электронные письма от разработчиков, обеспокоенных отсутствием у них опыта тестирования. Честно говоря, они должны волноваться! Тестирование не подлежит обсуждению.

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

Они не просто полагаются на один тип тестов, чтобы убедиться, что код работает.

Любые новые выпуски должны пройти через множество слоев. Все для создания предсказуемого программного обеспечения с минимальным количеством ошибок.

Вот что я имею в виду…

Базовый уровень: Модульные тесты

Во-первых, вы хотите убедиться, что ваши отдельные функции и классы ведут себя так, как вы ожидаете.

Модульные тесты — это основа любой стратегии многоуровневого тестирования.

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

Вот несколько (действительно тупых) юнит-тестов:

import unittest
from typing import Optional


def hello(name: Optional[str] = None) -> str:
    if name:
        return f'Hello {name}'
    return 'Hello world'


class TestHello(unittest.TestCase):
    def test_hello(self):
        self.assertEqual(hello(), 'Hello world')

    def test_hello_name(self):
        self.assertEqual(hello('John'), 'Hello John')

    def test_hello_empty_string(self):
        self.assertEqual(hello(''), 'Hello world')

Они проверяют, что моя функция дает ожидаемый результат.

Для каждой новой функции, которую вы кодируете, вы также должны написать множество модульных тестов. Даже для этой простой функции hello() я написал 3 модульных теста! Если вы пишете более сложное производственное программное обеспечение, вы должны написать массу тестов с разных точек зрения.

Модульные тесты должны быть быстрыми и многочисленными. Каждый тест должен проверять код только в отдельной «единице» вашего кода. Не проверяйте взаимодействие между сервисами или сложные пользовательские потоки в рамках модульного теста.

Чисто, просто и понятно.

Средний уровень: функциональные/интеграционные тесты

Затем мы добавим больше сложности и накладных расходов в наши тесты.

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

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

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

Вот интеграционный тест:

def test_database_insert_from_orm():
    db_objects = MyModel.objects.all()
    assert len(db_objects) == 0

    response = requests.post('/api/my_model', {'name': 'New Test Model'})
    assert response.status_code == 201

    db_objects = MyModel.objects.all()
    assert len(db_objects) == 1

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

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

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

Внешний слой: Сквозные тесты

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

Для сквозного (E2E) теста вы запускаете все приложение. Раскрутить все сервисы. Запустите все.

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

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

Эти тесты сложно написать, и они могут быть нестабильными, если какая-либо часть приложения изменится. Тем не менее, они стоят усилий, чтобы сделать их правильными. Ни один другой тип теста не дает полной уверенности в том, что пользователи увидят правильные результаты.

Почему слои имеют значение

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

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

Сквозные тесты действительно смотрят на общую картину. Они говорят вам что-то действительно важное — что пользователь увидит при использовании приложения. Вы можете автоматизировать большую часть существующего ручного тестирования с помощью нескольких отличных E2E-тестов. Однако их еще сложнее писать и медленнее запускать! Более того, вы не можете ничего утверждать о состоянии базы данных или базового кода. Только то, что видят пользователи.

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

Спас мою задницу

Буквально на этой неделе тест E2E обнаружил ошибку в моем коде, которую пропустили модульные и интеграционные тесты!

Когда я обнаружил ошибку, я сразу же написал для нее модульный тест (чтобы мы обнаружили ее раньше, без необходимости запускать медленные и дорогие E2E-тесты). Но эта ошибка была бы запущена в производство без наших автоматических шагов модульного тестирования, чтобы ее остановить!

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

Этот пост просто царапает поверхность. Хотите узнать больше о тестировании? Оставьте мне ответ.

Ежедневный список

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

Присоединяйтесь к Medium за 5 долларов — получите доступ ко всему Medium + поддержите меня и других!