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

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

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

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

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

Но жизнь продолжалась, функции разрабатывались, другие ошибки устранялись. Примерно в это же время мы также пробовали Percy.io для автоматического регрессионного тестирования. Заставить Перси работать было невероятно легко! Все, что нам нужно было сделать, это добавить одну строку в наши тесты, чтобы сделать снимок:

percySnapshot(“Some Component”);

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

Введите: приемочные испытания! Ember - активный сторонник хорошего покрытия кода (то есть тестирования вашего кода!). В нем есть 3 различных типа тестов, которые вы можете написать:

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

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

Я создал приемочный тест с помощью Ember CLI:

ember g acceptance-test some-route

И, к моему удивлению, сгенерированный приемочный тест не прошел!

Поэтому я сделал то, что делаю всегда: посмотрел на ошибки консоли и открыл тест в инструментах разработчика. Это были ошибки в консоли (в таком порядке):

Source:
TypeError: Cannot read property ‘lookup’ of undefined
at Object.initialize (http://localhost:4200/assets/my-app.js:10312:28)
at http://localhost:4200/assets/vendor.js:61627:21
at Vertices.each (http://localhost:4200/assets/vendor.js:80243:9)
at Vertices.walk (http://localhost:4200/assets/vendor.js:80157:12)
at DAG.each (http://localhost:4200/assets/vendor.js:80087:22)
at DAG.topsort (http://localhost:4200/assets/vendor.js:80095:12)
at Class._runInitializer (http://localhost:4200/assets/vendor.js:61653:13)
at Class.runInitializers (http://localhost:4200/assets/vendor.js:61625:12)
at Class._bootSync (http://localhost:4200/assets/vendor.js:59923:14)
at Class.boot (http://localhost:4200/assets/vendor.js:59890:14)
Source:
Error: Cannot call `visit` without having first called `setupApplicationContext`.
at visit (http://localhost:4200/assets/test-support.js:44177:13)
at Object._callee$ (http://localhost:4200/assets/tests.js:23:47)
at tryCatch (http://localhost:4200/assets/vendor.js:12365:40)
at Generator.invoke [as _invoke] (http://localhost:4200/assets/vendor.js:12591:22)
at Generator.prototype.<computed> [as next] (http://localhost:4200/assets/vendor.js:12417:21)
at asyncGeneratorStep (http://localhost:4200/assets/tests.js:6:105)
at _next (http://localhost:4200/assets/tests.js:8:196)
at http://localhost:4200/assets/tests.js:8:366
at new Promise (<anonymous>)
at Object.<anonymous> (http://localhost:4200/assets/tests.js:8:99)

В этот момент я заметил, что с вызовом `visit ()` определенно возникла проблема. Сообщение об ошибке казалось довольно простым, поэтому я надеялся, что это будет легко исправить!

После небольшого рытья и отладки я увидел, что что-то неправильно устанавливает контекст теста. Это был просто пустой объект:

Внутри вызова `visit (« / some-route »)` isApplicationTestContext (context) `вернул false, и возникла вторая ошибка сверху. Я предположил, что ошибка поиска была просто выброшена из инициализаторов нашего приложения, поскольку наше приложение создавалось для приемочного теста, но, возможно, это было просто из-за контекста приложения. Для меня более очевидной проблемой было второе сообщение об ошибке. Было ясно, что конкретная функция не вызывается.

После некоторого поиска в Google выяснилось, что обновление Ember не привело к обновлению файла test-helper.js. Наш файл устарел! Простое исправление - просто перезапишите старый файл, используя новый способ работы. Я надеялся, что это поможет.

Старый файл `test-helper.js` выглядел так:

import resolver from “./helpers/resolver”;
import { setResolver } from “@ember/test-helpers”;
import { start } from “ember-cli-qunit”;
setResolver(resolver);
start();

Я обновил его именно так, как должно быть:

import Application from “../app”;
import config from “../config/environment”;
import { setApplication } from “@ember/test-helpers”;
import { start } from “ember-qunit”;
setApplication(Application.create(config.APP));
start();

Большой! Я снова провел тесты. К моему удивлению, все наши тесты теперь терпели неудачу… Что ??

Я попытался повторно добавить вызов `setResolver`, но это не имело значения. Я попробовал поискать в Google файл помощника по тестированию и эту проблему с контекстом, но все, что я смог найти, это то, что, возможно, для параметра autoboot не было установлено значение false в файле среды. К сожалению, это тоже не имело значения.

Я оставил его в покое на остаток дня и вернулся к нему на следующий день. Что мне не хватало?

Вот факты:

  • При обращении к `visit` жаловался на контекст.
  • Произошла ошибка при вызове функции lookup.
  • Теперь вспомогательный тестовый файл был исправлен. Это был новый способ ведения дел, и он действительно не должен вызывать никаких проблем.

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

Ответ пришел ко мне после того, как я посмотрел на эту строку:

setApplication(Application.create(config.APP));

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

Одна часть головоломки была завершена. Сборка всего приложения Ember вызвала проблемы. Но почему?

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

А теперь немного предыстории:

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

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

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

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

Теперь решение поставленной проблемы.

Причина появления этой ошибки:

Source:
TypeError: Cannot read property ‘lookup’ of undefined
at Object.initialize (http://localhost:4200/assets/my-app.js:10312:28)
at http://localhost:4200/assets/vendor.js:61627:21
at Vertices.each (http://localhost:4200/assets/vendor.js:80243:9)
at Vertices.walk (http://localhost:4200/assets/vendor.js:80157:12)
at DAG.each (http://localhost:4200/assets/vendor.js:80087:22)
at DAG.topsort (http://localhost:4200/assets/vendor.js:80095:12)
at Class._runInitializer (http://localhost:4200/assets/vendor.js:61653:13)
at Class.runInitializers (http://localhost:4200/assets/vendor.js:61625:12)
at Class._bootSync (http://localhost:4200/assets/vendor.js:59923:14)
at Class.boot (http://localhost:4200/assets/vendor.js:59890:14)

из-за следующего кода:

export function initialize(application) {
  const container = application.__container__; // ← undefined
  const lookup = container.lookup.bind(application.__container__); // ← error!
  ...
}

Урок был усвоен в тот момент, когда я открыл инициализатор. Мы использовали `application .__ container__`!

В JavaScript любой метод или переменная, начинающаяся с подчеркивания, обычно считается закрытой. Два символа подчеркивания означают то же, что и один, но более выразительный - НЕ полагайтесь на него!

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

Что странно, приведенный выше код работает в средах разработки и производства. Это изменение, похоже, является результатом устаревания / перестановки внутренних API-интерфейсов Ember, особенно связанных с контейнером. См. Эту страницу для получения дополнительной информации.

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

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

Вместо этого код был перемещен в ловушку beforeModel () маршрута приложения. Я также добавил инициализатор экземпляра для службы Segment, что привело к более быстрой загрузке приложения.

После перемещения кода инициализатора в маршрут приложения приложение было успешно построено, все тесты прошли снова, и приемочные тесты работали как шарм. :)

Мораль рассказа / TL; DR:

Полагайтесь на вещи, которые меняются меньше, чем вы
- Санди Мец

Хотите оказать влияние на быстрорастущую технологическую команду? Взгляните на наши открытые должности и подайте заявку сегодня: ritual.com/careers