Information

Founded
Location
Россия
Website
www.ontico.ru
Employees
11–30 employees
Registered

Habr blog

Pull to refresh
Comments 8
Pixel perfect больше не в моде? А если у меня некоторые блоки подгружаются по fetch и встраиваются с анимацией при этом время подгрузки и окончательный рендеринг не всегда заканчивается в одно время, что зависит от межсерверного трансфера? Например Page Insights даже не всегда выдает скриншот до конца отрендеренным и жалуется на анимацию, как на время задержки рендера. Раньше в PP просто наложил PNG и сверяешь — предложенный инструмент корректно работает с динамическими интрефейсами? Например можно ли указать вызов модального окна при тестировании?

Постараюсь разбить текст на отдельные вопросы. Надеюсь не ошибусь и не пропущу ничего


Pixel perfect больше не в моде?

А зачем следовать моде? Нужно подходить к вопросу здраво:


  • Вашему проекту или вашим клиентам важен pixel perfect?
  • Дизайнеры сразу делают адаптивные решения, которые идеально живут на реальных устройствах пользователей?

Если да — возьмите инструмент для pixel-perfect сравнения вёрстки и макетов. Возьмите storybook и сделайте историю на каждое состояние макета, найдите/напишите свой аддон для pixel-perfect сравнения скриншота с эталонной картинкой из photoshop.


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

Можно:


  1. Отделить бизнес-логику от верстки. Тогда компоненты можно ставить в любые ситуации и проверять как они выглядят, прокидывая правильные пропсы
  2. Поднять стаб-сервер который будет отдавать всё быстро. Или использовать вместо стаб сервера msw, который зашивает мок сети в servive worker
  3. Подменить в тестовом окружении (jest\storybook) слой, который делает запросы, чтобы запросы возвращали данные из фикстур и моментально
  4. Настроить инструмент для скриншотов (loki/creevey) так, чтобы они дожидались конца запроса. loki.js из коробки дожидается конца всех асинхронных взаимодействий, если мне память не изменяет.

Зависит от того, что вы хотите проверить. Анимацию, процесс загрузки, конечное состояние, все сразу?


Например можно ли указать вызов модального окна при тестировании?

creevey позволяет писать полноценные тесты в рамках сторибука (с кликами по кнопкам, вводом данных в инпуты, ожиданиями и тп). В других инструментах, как правило, есть способ сказать ранеру, когда история готова к скриншоту.


Либо можно написать функциональный тест на cypress/testcafe/selenium и там сделать скриншот в удобный вам момент.
Можно на уровне пропсов выделить состояние "модалка открыта" и сделать скриншот с открытой модалкой, а в функциональном тесте убедится что модалка открывается по клику.

Интеграционные тесты позволяют проще рефакторить внутренности разных модулей… Очень спорное утверждение… По факту функциональный тест из статьи зависит и от разметки (h3, div), и от контента («Найти»), и от endpoint url ('api/search'). А мы проверили всего один простейший сценарий.

По своему опыту могу сказать, что такого рода тесты (функциональные, интеграционные, e2e) очень хрупкие. Ломаются неожиданно, фиксятся не очевидно, всякие проблемы с await waitRerendering(), await waitMounting(), setTimeout. И писать моки и pageObjects не радует, это сложные низкоуровневые манипуляции, как их не пряч. Даже переиспользование утильных функций для тестов в перспективе ни к чему хорошему не приведёт.

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

Выделяйте модель, пишите на все кейсы юниты, пишите один-два интеграционных (e2e), чтобы убедиться, что модель к компоненту приклеилась ровно, и пейте кофе.

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

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

Пример юнит тестов
interface SearchApi {
  search(term: string): Promise<SearchResult>;
}

interface SearchResult {}

class SearchPresenter {
  pending: boolean = false;

  result?: SearchResult;

  constructor(private api: SearchApi) {}

  async search(term: string): Promise<void> {
    try {
      this.pending = true;
      this.result = await this.api.search(term);
    } finally {
      this.pending = false;
    }
  }
}

describe(SearchPresenter.name, () => {
  describe(SearchPresenter.prototype.search.name, () => {
    it('should resolve result', async () => {
      const result = {} as SearchResult;
      const searchPresenter = new SearchPresenter({ search: async () => result });
      await searchPresenter.search('term');
      expect(searchPresenter.result).toBe(result);
    });

    it('should change pending', async () => {
      const searchPresenter = new SearchPresenter({ search: async () => ({} as SearchResult) });
      const searchPromise = searchPresenter.search('term');
      expect(searchPresenter.pending).toBe(true);
      await searchPromise;
      expect(searchPresenter.pending).toBe(false);
    });

    it('should set pending to false on error', async () => {
      const searchPresenter = new SearchPresenter({ search: () => Promise.reject({}) });
      await searchPresenter.search('term').catch(() => {});
      expect(searchPresenter.pending).toBe(false);
    });

    it('should preserve prev result on error', async () => {});

    it('should use last search term on double call', async () => {});
  });
});


По факту функциональный тест из статьи зависит и от разметки (h3, div), и от контента («Найти»), и от endpoint url ('api/search').

Можно сказать и так. По факту тест и должен зависеть от каких-то внутренних имлпементация. Ведь мы хотим, чтобы если мы изменили что-то важное (например url ендпоинта), то тест бы упал. Я не хотел бы опираться на h3, div, "Найти", но пока тестовым фреймворкам нельзя сказать "найди там кнопку, максимально похожую на сабмит, и кликни её", приходится писать как минимум текст этой кнопки.


Я всего-лишь предлагаю не описывать такие вещи (h3, div, "Найти", url) явно в тесте и не делать низкоуровневые тесты. Тогда и тесты будут читаемые, и при правках этих признаков страницы (h3, div, "Найти", endpoint) не придется менять тест, достаточно будет поменять только pageObject, который обновится во всех тестах разом.


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

Я в докладе упоминал, что термин unit — очень неудачный. Для меня, мои функциональные тесты — это юнит-тесты. Но я вокруг себя никого в этом убедить не могу, все говорят, что это интеграционные тесты.


Если у вас проблемы с юнитами, значит вы что-то делаете неправильно. А обмазывание снепшотами, e2e тестами, переворачивание пирамид — это самоуспокоение.

"Если ваша система хорошо покрывается тестами — значит ваша система хорошо спроектирована" — это очень популярный миф.


Есть и обратное мнение, что юнит-тесты не нужны:



Это только те ссылки, которые я без проблем могу найти в своей коллекции ссылок на raindrop.io.


К сожалению, мир тестирования чуть сложнее, чем "юниты-интеграционные-e2e". Если бы мы представляли тестирование в виде пирамиды, это определенно была бы как минимум 3D пирамида, а не простой треугольник.


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


Известные проблемы низкоуровневых тестов:


  • мешают рефакторить (делать изменения кода без изменения поведения). В вашем примере я не могу заменить булевый pending на state: 'pending' без изменения тестов. Хотя при функциональных тестах я мог бы поменять эту часть реализации и быть уверенным, что если тесты проходят — значит я совершил безопасный рефакторинг. С рефакторингами сложнее чем изменение поля, например выделение из одной сущности двух разных сущностей, низкоуровневые тесты можно смело выкидывать
  • Низкоуровневных тестов нужно писать много. Кроме самого поведения, как минимум на каждую связь двух модулей нам нужны тесты взаимодействия этих сущностей. Как следствие предыдущего пункта, их нужно будет исправлять при нулевых, с точки зрения функционала, изменениях.
  • Они дают слабые гарантии работы приложения.

В реальности же часто бывает, что сложность написания низкоуровневых тестов и функциональных тестов одинакова. Т.е. нам часто ничего не стоит вместо прямого вызова метода какой-нибудь сущности, переписать немного arrange, act и assert фазы теста так, чтобы мы кликали на кнопку и проверяли html/вызов spy'ев.


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

В последних проектах, в которых я работал, у меня всегда были отделены данные от view. Я всегда мог написать кучу юнит-тестов (тест на сервис, тест на стор, тест на компонент, тест на коннектор, тест на отсылку метрик), но это всегда было нецелесообразно, мешало разработке и не гарантировало работу фичи. В результате экспериментов всегда получалось придти к ситуации "пишем меньше тестов, но уверенности в работе кода больше" и другие разработчики с радостью это перенимали.


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


Главное в этом деле:


  • думать своей головой
  • не упарываться
  • писать тесты. Юниты, e2e, плохие, хорошие — любые, пока они приносят вам пользу.
  • эксперементировать, чтобы найти наилучшие способы тестирования функционала в вашем контексте/проекте.
Спасибо за развернутый ответ! Да, интеграционные тесты игнорирует некоторый слой деталей реализации. Это можно использовать во благо.

найди там кнопку, максимально похожую на сабмит, и кликни её


Я использовал атрибуты testId=«sumbit-button».

Я использовал атрибуты testId=«sumbit-button».

и к этому тоже есть альтернативное мнение, что не нужно использовать data-test-id для всего подряд.


tldr: пользователи не видят ваших data-test-id, они оперируют кнопками, заголовками и текстами — оперируйте и вы.


По моему опыту, не сильно важно использовать data-test-id или сразу матчится на текст/label/etc в html. Сделать любой из этих селекторов очень просто и занимает очень мало времени, относительно проектирования теста и написания кода.
Хотя для компонентов больше 3х контролов, я обычно описываю контролам data-test-id

Для мока API есть mirage.js. Не привязана к какой-то конкретной библиотеке, будет работать и с axios и с голым fetch. Однажды довелось попользоваться по причине ещё не готовой апишки на проекте, впечатления положительные

Выглядит круто!


Из непривязанных к библиотеке я знаю msw и nock, но сам использую обычно nock. Надо попробовать mirage.js.

Only those users with full accounts are able to leave comments. Log in, please.