Как инженеры, мы хотим создавать вещи , которые работают , но с каждой новой функцией, которую мы создаем, мы неизбежно увеличиваем размер и сложность наших приложений.
По мере роста продукта становится все больше и больше времени уходит на ручное (например, руками) тестирование каждой функциональности, на которую влияют ваши изменения.
Отсутствие автоматизированных тестов приводит к тому, что мы либо тратим слишком много времени и замедляем скорость доставки, либо тратим слишком мало, чтобы сохранить скорость, что приводит к появлению новых ошибок в бэклоге и ночным звонкам от PagerDuty.
Напротив, компьютеры можно запрограммировать на многократное выполнение одного и того же . Итак, давайте делегируем тестирование компьютерам!
Идея пирамиды тестирования предполагает три основных типа тестов: модульные, интеграционные и сквозные . Давайте углубимся в каждый вид и поймем, почему каждый из них нам нужен.
Модуль — это небольшой фрагмент логики, который вы тестируете изолированно (не полагаясь на другие компоненты).
Модульные тесты выполняются быстро. Они заканчиваются в течение нескольких секунд. Изоляция позволяет им запускать их в любой момент времени, локально и в CI, без запуска зависимых сервисов/выполнения вызовов API и базы данных.
Пример модульного теста: функция, которая принимает два числа и суммирует их. Мы хотим вызвать его с разными аргументами и подтвердить, что возвращаемое значение правильное.
// Function "sum" is the unit const sum = (x, y) => x + y test('sums numbers', () => { // Call the function, record the result const result = sum(1, 2); // Assert the result expect(result).toBe(3) }) test('sums numbers', () => { // Call the function, record the result const result = sum(5, 10); // Assert the result expect(result).toBe(15) })
Более интересный пример — компонент React, который отображает некоторый текст после завершения запроса API. Нам нужно смоделировать модуль API, чтобы он возвращал необходимые значения для наших тестов, визуализировал компонент и утверждал, что визуализированный HTML содержит необходимое нам содержимое.
// "MyComponent" is the unit const MyComponent = () => { const { isLoading } = apiModule.useSomeApiCall(); return isLoading ? <div>Loading...</div> : <div>Hello world</div> } test('renders loading spinner when loading', () => { // Mocking the API module, so that it returns the value we need jest.mock(apiModule).mockReturnValue(() => ({ useSomeApiCall: jest.fn(() => ({ // Return "isLoading: false" for this test case isLoading: false })) })) // Execute the unit (render the component) const result = render(<MyComponent />) // Assert the result result.findByText('Loading...').toBeInTheDocument() }) test('renders text content when not loading', () => { // Mocking the API module jest.mock(apiModule).mockReturnValue(() => ({ useSomeApiCall: jest.fn(() => ({ // Return "isLoading: false" for this test case isLoading: false })) })) // Execute the unit (render the component) const result = render(<MyComponent />) // Assert the result result.findByText('Hello world').toBeInTheDocument() })
Когда ваш модуль взаимодействует с другими модулями (зависимостями) , мы называем это интеграцией . Эти тесты медленнее, чем модульные тесты, но они проверяют, как соединяются части вашего приложения.
Пример интеграционного теста: служба, которая создает пользователей в базе данных. Для этого требуется, чтобы экземпляр БД ( зависимость ) был доступен при выполнении тестов. Мы проверим, что сервис может создавать и извлекать пользователя из БД.
import db from 'db' // We will be testing "createUser" and "getUser" const createUser = name => db.createUser(name) // creates a user const getUser = name => db.getUserOrNull(name) // retrieves a user or null test("creates and retrieves users", () => { // Try to get a user that doesn't exist, assert Null is returned const nonExistingUser = getUser("i don't exist") expect(nonExistingUser).toBe(null); // Create a user const userName = "test-user" createUser(userName); // Get the user that was just created, assert it's not Null const user = getUser(userName); expect(user).to.not.be(null) })
Это сквозной тест, когда мы тестируем полностью развернутое приложение , в котором доступны все его зависимости. Эти тесты лучше всего имитируют реальное поведение пользователя и позволяют выявить все возможные проблемы в вашем приложении, но это самый медленный тип тестов.
Всякий раз, когда вы хотите запустить сквозное тестирование, вы должны подготовить всю инфраструктуру и убедиться, что сторонние поставщики доступны в вашей среде.
Вы хотите использовать их только для критически важных функций вашего приложения.
Давайте рассмотрим пример сквозного теста: Процесс входа в систему. Мы хотим зайти в приложение, заполнить данные для входа, отправить их и увидеть приветственное сообщение.
test('user can log in', () => { // Visit the login page page.goto('https://example.com/login'); // Fill in the login form page.fill('#username', 'john'); page.fill('#password', 'some-password'); // Click the login button page.click('#login-button'); // Assert the welcome message is visible page.assertTextVisible('Welcome, John!') })
Помните, что сквозные тесты медленнее интеграционных , а интеграционные тесты медленнее модульных .
Если функция, над которой вы работаете, является критически важной, рассмотрите возможность написания хотя бы одного сквозного теста (например, проверки того, как работает функция входа в систему при разработке потока аутентификации).
Помимо критически важных потоков, мы хотим протестировать как можно больше крайних случаев и различных состояний функции. Интеграционные тесты позволяют нам проверить, как части приложения работают вместе.
Хорошей идеей является проведение интеграционных тестов для конечных точек и клиентских компонентов. Конечные точки должны выполнять операции, давать ожидаемый результат и не выдавать неожиданных ошибок.
Клиентские компоненты должны отображать правильный контент и реагировать на взаимодействие с пользователем так, как вы ожидаете от них.
И, наконец, когда нам следует выбирать модульные тесты ? Все небольшие функции, которые можно тестировать изолированно, например sum
, суммирующий числа, Button
, отображающий тег <button>
, являются отличными кандидатами для модульных тестов. Модули идеальны, если вы следуете подходу разработки через тестирование .
Напишите несколько тестов! (но начните с малого)
Сделайте все вышеперечисленное один раз, чтобы понять, как это работает. Затем сделайте это снова во время работы над какой-либо функцией или ошибкой. Тогда поделитесь им со своими коллегами, чтобы вы все писали тесты, экономили время и лучше спали по ночам!