Я намерен написать это, чтобы поделиться своим опытом написания автоматических тестов программного обеспечения в первый раз и записать свое понимание концепций, которые я изучил до сих пор. Мой опыт в том, что я разработчик-самоучка; Я научился программировать в Free Code Camp (FCC) онлайн, а также на встречах манчестерского отделения FCC. Я надеюсь, что, описывая свои мыслительные процессы, трудности, с которыми я столкнулся, когда начал заниматься тестированием в качестве новичка, и свой текущий уровень знаний, я смогу создать полезную запись. Оно не предназначено для использования в качестве авторитетного руководства для разработчиков, плохо знакомых с тестированием; мое понимание предмета все еще находится в стадии разработки!

Статья посвящена тестированию кода JavaScript (JS), написанного с использованием библиотеки React. Сами тесты были написаны с использованием инструментов тестирования Jest и Enzyme. Некоторые части специфичны для React, но я думаю, что многое из этого будет иметь отношение к любому тестированию JS или даже к тестированию на других языках. Вы можете найти код, который я тестирую, включая сами тесты, в этом репозитории GitHub. Приложение представляет собой простой веб-менеджер задач. Он показывает задачи, которые уже были добавлены, и позволяет вам обновлять их, а также дает вам возможность добавлять новые задачи. Вы можете увидеть его в действии здесь (поскольку он развернут на бесплатном плане Heroku, его открытие может занять несколько секунд).

Немного теории

Тестирование является важной частью профессиональной разработки программного обеспечения. Хорошо написанные и надежные автоматические тесты могут сыграть большую роль в сокращении количества ошибок, попадающих в рабочую среду, и дают разработчикам больше уверенности в том, что внесение изменений и новых функций не нарушит уже существующий код. На самом деле, существует подход к написанию программного обеспечения под названием Test Driven Development (TDD), который предусматривает, что разработчики сначала пишут тесты, прежде чем писать какой-либо другой код. После того, как тесты написаны, цель состоит в том, чтобы заставить их пройти, написав код приложения.

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

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

  • Модульные тесты
  • Интеграционные тесты
  • Сквозные тесты

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

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

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

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

Пытаюсь понять, как применить теорию на практике.

После того, как я немного прочитал теорию, мне захотелось начать применять ее на практике и самому написать несколько тестов. Это оказалось немного сложнее, чем я изначально думал, что это будет. Я слышал об инструментах тестирования Mocha и Chai, поэтому начал с того, что попытался протестировать свой код React с их помощью. Mocha и Chai — это, по сути, библиотеки, которые предоставляют множество функций для написания тестов на JS. Mocha — это тестовая среда, что означает, что она отвечает за фактическое выполнение тестов, а также сообщает, были ли тесты пройдены или не пройдены. Chai — это библиотека утверждений, и она используется для проверки того, являются ли значения, которые выдают наши функции, теми, которые мы ожидаем. Mocha сам предоставляет некоторые функции утверждения, но предложение Chai богаче в этой области. (Если сейчас вы думаете, что хотели бы увидеть, как тесты выглядят в реальном коде, не беспокойтесь; я приведу несколько примеров чуть ниже).

Вскоре я столкнулся с проблемой. Мой код React работает, добавляя компоненты в DOM (объектная модель документа) и впоследствии обновляя их в зависимости от взаимодействия пользователя с веб-страницей. Как я собирался проверить, что, например, мой код отображает форму с определенными предустановленными значениями, а затем обновляет значения на основе ввода пользователя? Казалось, что моя главная проблема заключалась в том, что мне не хватало DOM — я не мог проверить вывод своего кода. Это осознание заставило меня переоценить инструменты, которые я пытался использовать для написания тестов. После небольшого исследования и информативного выступления @peterdaily на встрече FCC в Манчестере я решил переключиться на использование инструментов тестирования Enzyme и Jest. Enzyme — это утилита для тестирования для React, и я выбрал ее, потому что она специально разработана для тестирования компонентов React и поставляется с виртуальной DOM. Он включает в себя встроенные методы для основных практик React, таких как обновление состояния компонента и передача свойств. Эти методы можно использовать при тестировании для воспроизведения поведения компонентов во время выполнения кода приложения. Виртуальный DOM дает нам возможность моделировать отрисовку компонентов на веб-странице, а также моделировать взаимодействие пользователя с компонентами. Jest, как и Mocha, — это среда тестирования, и я слышал, что она хорошо работает с Enzyme. Также оказалось, что он уже установлен в моем проекте, потому что я создал исходный фреймворк для клиентского кода с помощью Create React App.

Как только я настроил свой проект на использование Jest и Enzyme (вы можете посмотреть документацию по этим проектам, если хотите знать, как это сделать; оба они довольно просты в настройке), я столкнулся с другой трудностью. Я действительно не знал, на что мне следует тестировать свои компоненты. Это важная часть тестирования; чтобы тесты имели ценность, нам нужно тестировать правильные вещи. Я спросил об этом на канале FCC Manchester Slack, и @peterdaily порекомендовал эту статью Free Code Camp о том, как тестировать компоненты React. Автор статьи подчеркивает, что важно начать с размышлений о том, как работают наши компоненты и какие их аспекты мы должны тестировать, а какие нет (интересно, она говорит, что теперь предпочитает интеграционные тесты модульным тестам, когда тестирование приложений React.Это интересная дискуссия, но здесь она выходит за рамки моей компетенции). Из статьи я выделил следующие вопросы как важные:

  • Что отображает компонент?
  • Какие реквизиты компонент передает дочерним компонентам?
  • Что компонент делает с любым реквизитом, который он получает?
  • Что компонент хранит в состоянии и как оно обновляется?
  • Что происходит, когда пользователь взаимодействует с компонентом?

Имея в виду эти руководящие вопросы, я просмотрел свои компоненты по отдельности и записал то, что я хотел их протестировать (я исправлял и расширял это несколько раз, когда писал тесты). Я также смог прочитать авторский тест. script и, следовательно, посмотрите, что она сама тестировала в качестве руководства (это помогло мне разработать синтаксис тестов, написанных с использованием Jest и Enzyme). Теперь я наконец был готов написать несколько тестов.

Написание тестов

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

describe(“the value of the newTaskTitle prop”, () => {
  test(“matches the value held in state”, () => {
    const newTaskForm = app().find(NewTaskForm);
    expect(newTaskForm.props().newTaskTitle)
      .toBe(app().state().newTaskTitle);
  });
});

Это относительно простой тест, который дает нам хорошую возможность поближе познакомиться с некоторыми основами тестирования. Самая внешняя функция — это блок describe. Он используется для группировки отдельных связанных тестов. На самом деле не обязательно включать блок describe в тестовый сценарий Jest, но он помогает сделать тесты более читабельными, разделяя их на отдельные группы (это также позволяет настраивать тестовую среду только для определенной группы). тестов - мы увидим это в действии ниже). Функция test, вложенная в блок описания, фактически выполняет тест (если хотите, вместо test в качестве ключевого слова можно использовать it, но эффект тот же). Вы заметите, что для обеих функций describe и test первый аргумент, который я передаю, является описательной строкой. Значения, которые вы присваиваете этим строкам, не влияют на фактическое выполнение теста — вы можете передать пустую строку, если хотите, — но они являются очень важной частью тестового сценария, потому что они дают нам возможность сделать тесты читаемы. Я следовал шаблону использования строки описания, чтобы указать, какую часть моего кода я тестирую, и тестовой строки, чтобы указать, для чего я ее тестирую. Удобочитаемость имеет решающее значение, потому что она позволяет любому, кто читает тестовый сценарий или его вывод, точно понять, что тестируется, и упрощает определение того, что идет не так с неудачными тестами. Читаемые и описательные тестовые сценарии также могут служить документацией для кода. Я по-настоящему оценил удобочитаемость тестов, когда у меня была возможность посетить несколько занятий в клубе кодирования, организованном командой Digital в Coop, моем бывшем работодателе. Во время сессий мы все работали вместе, чтобы написать тесты для приложения (это был подход «группового программирования»). Я всегда рекомендую воспользоваться возможностью посмотреть, как работают более опытные разработчики, если у вас есть такая возможность.

Второй аргумент, передаваемый функции test, — это функция обратного вызова, содержащая тест. Функции expect и toBe объединяются для создания утверждения. Оба принимают значения в качестве аргументов, и отношение этих значений друг к другу определяет, прошел ли тест или нет. Значение, которое передается expect, должно быть значением, которое вы хотите проверить. Функция toBe известна как сопоставитель, и значение, которое вы передаете ей, должно быть тем, с которым вы хотите сравнить ожидаемое значение. В случае моего теста выше я ожидаю, что два значения будут равны друг другу; если это не так, тест провалится. Jest предоставляет большое количество различных сопоставителей, включая те, которые проверяют, является ли значение меньше или больше другого значения, является ли оно неопределенным, является ли оно истинным или ложным, и многие другие.

До сих пор все, что я описал, было функциональностью Jest. Enzyme вступает в игру, предоставляя значения, которые передаются функциям expect и toBe. Значения получены из моих компонентов. Как я упоминал ранее, Enzyme визуализирует мои компоненты в виртуальный DOM, что позволяет мне проверить, как они будут себя вести, если они будут визуализированы на реальной веб-странице. Сюда входит чтение значений таких свойств, как состояние и свойства. В приведенном выше коде я проверяю, соответствует ли реквизит, который компонент передает дочернему компоненту, состоянию родительского компонента.

Давайте взглянем на другую часть моего тестового сценария:

describe("when a Task is returned by the initial GET request", () => {
  beforeEach(() => {
    const mockTask = {
      task_id: 5,
      task_creation_dt: "2020-03-30T14:24:21.437Z",
      task_title: "Finish French Homework",
      task_desc: "Involves some verb conjugation",
      task_completed: false,
      task_scheduled_dt: "2020-04-25T00:00:00.000Z",
      priority_desc: "High",
    };
    app().setState({ tasks: [mockTask] });
  });

  test("a Task component is rendered", () => {
    const tasks = app().find(Task);
    expect(tasks.length).toBe(1);
  });

  test("the number of Task components rendered matches the number of task objects returned by the API", () => {
    const tasks = app().find(Task);
    expect(tasks.length).toBe(app().state("tasks").length);
  });

Это пример изменения тестовой среды перед запуском тестов, чтобы мы могли протестировать приложение в определенных заданных условиях. Это называется настройка. Здесь я использую функцию beforeEach, предоставленную Jest, для выполнения настройки. Функция обратного вызова, которая передается в beforeEach, будет выполняться перед каждым тестом в блоке describe; это означает, что мне не нужно повторяться, записывая установочный код для каждого отдельного теста в этом блоке. Обратный вызов не будет выполняться перед любым другим тестом в сценарии, который не находится в этом блоке describe, что позволяет мне выборочно применять настройку. В этом случае я использую beforeEach для изменения состояния одного из моих компонентов, чтобы я мог запускать некоторые тесты при этом конкретном условии (возможность изменять состояние компонента предоставляется Enzyme). Teardown — это зеркало установки; это для тех случаев, когда вам нужно выполнить некоторые работы по очистке после выполнения тестов. Функция Jest afterEach запустит код после завершения тестов.

Когда мы тестируем, очень важно держать под контролем среду тестирования и чтобы побочные эффекты от одного теста не распространялись на остальную часть тестового сценария. Например, я бы не хотел, чтобы изменение состояния, которое я делаю в приведенном выше примере, применялось ко всем моим тестам. Функции beforeEach и afterEach являются ключевыми инструментами как для изменения, так и для последующего сброса среды. На самом деле, в начале обоих моих тестовых сценариев я использую функцию beforeEach, чтобы гарантировать, что тестируемый компонент будет удален из виртуального DOM перед запуском каждого теста в сценарии. Затем он добавляется в виртуальный DOM (в Enzyme это называется mounting) в теле каждого теста, поэтому я всегда работаю с чистой средой. Я считаю очень хорошей практикой выполнять такой сброс для каждого отдельного теста, потому что это дает вам уверенность в том, что тест проходит или не проходит сам по себе, а не из-за скрытого побочного эффекта от другого теста. .

Я привел эти два примера, чтобы дать представление о том, как выглядят модульные тесты, и представить некоторые фундаментальные концепции. Существует множество других функций модульного тестирования, которые не описаны в моих примерах (и мои собственные знания еще не распространяются на все эти функции). Когда я писал свои тестовые сценарии, основной способ обучения заключался в том, чтобы начать с идеи о том, что я хочу протестировать, а затем прочитать документацию Jest и Enzyme, чтобы понять, как это сделать. Если я все еще не был уверен в чем-то, я искал в Интернете. Я обнаружил, что по мере того, как я тратил больше времени на написание своих тестов, я стал лучше понимать логику тестирования, и путаница, которая у меня была в начале, начала уменьшаться. Когда у меня будет больше опыта, я ожидаю, что оглянусь на свою первую попытку и увижу ошибки и то, что можно было сделать лучше. Это все часть процесса обучения, и это не должно быть обескураживающей мыслью.

Пройдены ли мои тесты?

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

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

В приведенном выше примере все мои тесты прошли. Теперь давайте посмотрим, какой вывод выдает Jest, когда один из моих тестов терпит неудачу:

Jest предоставляет мне подробную информацию о неудачных тестах, в том числе распечатывает значения ожидаемых и полученных. Ожидаемое значение — это то значение, которое мы передаем нашей функции сопоставления (функция сопоставления в приведенном выше случае — toBe), в то время как значение, которое мы фактически получаем — полученное из кода, который мы тестируем, — передается функции expect. Тот факт, что Jest выводит эти значения, означает, что у меня есть хорошая отправная точка для решения проблемы. В этом случае я ясно вижу, что мой тест ожидал, что компоненту будут переданы 3 реквизита, хотя на самом деле он прошел 4.

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

Сколько тестов я сделал?

Если вы прочитаете мои тестовые сценарии, то заметите, что я тестирую два больших компонента, а не меньшие. Честная причина этого в том, что, потратив довольно много времени на написание тестов для двух более крупных компонентов, я почувствовал, что хочу сделать перерыв в тестировании и какое-то время поработать над чем-то другим. Тем не менее, расширение объема тестов входит в мой список дел. Есть также фундаментальная часть моего приложения, которую я не тестирую, и это вызовы API. Мое приложение извлекает данные с сервера и дает пользователю возможность вносить обновления и добавлять новые данные. Насколько я понимаю, тестирование совместной работы клиентского и серверного кода подпадает под интеграционное тестирование. Однако, оставаясь в рамках модульного тестирования, с помощью Jest можно «издеваться» над вызовами API. Имитация запроса данных (то есть запроса GET) в этом случае будет включать создание функции только для целей тестирования, которая возвращает некоторые фиктивные данные, а не пытается подключиться к серверу. Затем я мог бы проверить, как мой клиентский код обрабатывает результат вызова API. Практика создания фиктивной функциональности для замены реальной функциональности, которая по какой-либо причине выходит за рамки нашей тестовой среды, является ключевой в мире тестирования. Однако, когда я писал свои тесты, я не продвинулся так далеко в своем обучении. Это то, что я планирую исправить в будущем! На самом деле существует термин, обозначающий, какая часть нашего кода покрыта тестами, которые мы пишем; это называется покрытие тестами и выражается в процентах.

Что я вынес из опыта?

Одно из больших преимуществ, которое я получил от написания тестов для своего приложения, заключается в том, что это заставило меня глубже задуматься о том, как был структурирован мой код. Чтобы решить, какие тесты написать, я внимательно изучил свое приложение, уделив особое внимание тому, как все это сочетается друг с другом и каковы ключевые элементы. В случае с моим приложением диспетчера задач я написал тесты после того, как написал код приложения. Однако теперь я решил применить подход TDD к новым функциям. В настоящее время я работаю над добавлением некоторых функций сортировки во внешний интерфейс приложения, и я начинаю с написания тестов, прежде чем писать новый компонент. Этот подход помогает мне продумать, как я на самом деле собираюсь реализовать функциональность. Например, это заставляет меня задуматься о том, как сортировка должна обрабатывать нулевые значения. Еще одно ключевое преимущество, пожалуй, самое очевидное; мои тесты уменьшают количество ошибок в моем приложении! Недавно я провел свои тесты после внесения изменений в код, и они выявили ошибку, которую я пропустил.

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

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

Если у вас есть какие-либо комментарии к тому, что я здесь написал, если вы заметили, что я что-то не так, или вы просто хотите еще немного обсудить вещи, я буду рад услышать от вас!

Электронная почта: [email protected]
Твиттер: @RossMorran

Сайт моего портфолио:https://ross117.github.io/

Первоначально опубликовано на https://fcc-mcr.now.sh.