Почему я сейчас ценю тестирование, и почему вы тоже должны.

1656611417 pochemu ya sejchas czenyu testirovanie i pochemu vy tozhe dolzhny

от Эвелин Чен

V26crRt1no9-dFumjEK77jTLpwRKTtGBPKAb
Фото Unsplash.com

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

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

Быстрое ознакомление с тем, как выглядит тестовый стек

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

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

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

Вот что я узнал

Тестирование предотвращает регрессию

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

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

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

Модульный тест с помощью макетов и заглушек.

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

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

К примеру, предположим, что вы хотите проверить такой метод:

import database from 'Database';import cache from 'Cache';
const getData = (request) => { if (cache.check(request.id)) { // check if data exists in cache  return cache.get(request.id); // get from cache } return database.get(request.id); // get from database};

Заглушка:

test('should get from cache on cache miss', (done) => { const request = { id: 10 }; cache.check = jest.fn(() => false);
getData(request); expect(database.get).toHaveBeenCalledWith(request.id); done();});

макет:

test('should check cache and return database data if cache data is not there', (done) => { let request = { id: 10 }; let dummyData = { id: 10, name: 'Foo' }  let cache = jest.mock('Cache'); let database = jest.mock('Database'); cache.check = jest.fn(() => false); database.get = jest.fn(() => dummyData);
expect(getData(request)).toBe(dummyData); expect(cache.check).toHaveBeenCalledWith(request.id); expect(database.get).toHaveBeenCalledWith(request.id); done();});

Основное отличие между ними состоит в состоянии против поведенческих манипуляций.

Используя макет, вы заменяете весь модуль фиктивным объектом. Заглушка – это принудительный выход функции независимо от заданного входа. Макеты используются для проверки того, вызывается ли функция с правильными аргументами, а заглушки используются для проверки того, как функция работает с заданным ответом. Заглушки используются для проверки состояния метода, в то время как макеты используются для оценки поведения.

Шутка обеспечивает jest.fn, имеющий как основные функции насмешки, так и заглушения. Макет Jest также может выводить метод заглушки, и в этом случае он может быть и фиктивным, и заглушенным.

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

Знайте что вам не нужно тестировать.

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

С помощью Jest вы можете легко отслеживать свое тестовое покрытие, добавив a--coverage тег к вашему тестовому сценарию в вашем CLI. Несмотря на то, что это полезное мероприятие, относитесь к этому с зерном соли – способ Jest измеряет тестовое покрытие через отслеживание стека вызовов, поэтому большее охват тестом не обязательно означает, что ваши тесты эффективны.

К примеру, в предыдущем проекте я использовал библиотеку для реализации компонента карусели. В компоненте была функция воспроизведения списка на основе массива. Чтобы увеличить охват тестом, я написал тест для подсчета и сравнения количества отображаемых элементов с массивом. Компонент карусели изменил количество элементов, отображаемых в DOM, чтобы вывести более 1:1, несмотря на то, что реализация визуально отображала правильное количество элементов в браузере. Я решил отказаться от тестового покрытия, потому что оно, по сути, тестировало библиотеку карусели, а не мой код.

Предположим, а Listings компонент с методом renderCarousel воспроизводящая карусель из внешней библиотеки:

Неэффективный тест:

test('should return the same number of elements as the array', (done) => {    // Full DOM render    let mountWrapper = mount(<Listings />);
    // State change to trigger re-render    mountWrapper.instance().setState({ listings: [listing, listing, listing] });
    // Updates the wrapper based on new state    mountWrapper.update();
    expect(mountWrapper.find('li').length).toBe(3);    done();  })

Эффективный тест:

test('should call renderCarousel method when state is updated', (done) => {    // Mock function inside component to track calls    wrapper.instance().renderCarousel = jest.fn();
    // State change to trigger re-render    wrapper.instance().setState({ listings: [listing, listing, listing] });
    expect(wrapper.instance().renderCarousel).toHaveBeenCalled();    done();  });

Разница между ними состоит в том, что на самом деле проверяют тесты.

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

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

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

Хорошо разработанные тесты ведут к хорошо разработанному коду.

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

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

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

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

Слишком много логики:

// Define endpointapp.get('/project', (request, response) => {  let { id } = request.params;  let query = `SELECT * FROM database WHERE id = ${id}`;    database.query(query)    .then((result) => {      const results = [];      for (let index = 0; index < data.length; index++) {        let result = {};        result.newKey = data[index].oldKey;        results.push(result);      }      response.send(results);    })    .catch((error) => {      response.send(error);    })  })

Легче проверить:

// Make call to database for dataconst getData = (request) => {  return new Promise((resolve, reject) => {    let { id } = request.params;    let query = `SELECT * FROM database WHERE id = ${id}`;    database.query(query)      .then(results => resolve(results))      .catch(error => reject(error));    };}
// Format data to send backconst formatData = (data) => {  const results = [];  for (let index = 0; index < data.length; index++) {    let result = {};    result.newKey = data[index].oldKey;    results.push(result);  }  return results;}
// Send back dataconst handleRequest = (request, response) => {  getData(request)  .then((result) => {    let formattedResults = formatData(result)    response.send(formattedResults);  .catch((error) => {    response.send(error);}
// Define endpointapp.get('/project', handleRequest);

Хотя второй пример длиннее, его гораздо легче следовать. Логика четко отвлечена и изолирована, что значительно облегчает ее проверку.

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

Пишите тесты во время кодирования

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

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

В системе с четко абстрагированными API вы можете проверить каждый класс, прежде чем двигаться дальше, чтобы знать, какая часть логики нарушена. К примеру, моя конечная точка «get» вызывает getData для взаимодействия с базой данных. Я бы сначала написал тесты для getData и убедился, что они зеленые. Итак, я знаю, что если любой из моих тестов контроллера не удается, это, вероятно, связано с тем, как я вызываю getData.

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

https://martinfowler.com/articles/mocksArentStubs.html

https://medium.com/@rickhanlonii/understanding-jest-mocks-f0046c68e53c

Если вам понравилась статья, нажмите на ?? и поделитесь, чтобы помочь другим найти его. Спасибо, что прочли!

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *