Недавно мы опробовали Serverless с AWS Lambdas в качестве технологического стека для проекта. До этого я последний раз сталкивался с Javascript в прошлом году, когда мы делали приложение React (тогда я ничего не знал о реакции). А до этого моя последняя встреча была в 2015 году, когда я работал над проектом Backbone.js.

В проекте React мы использовали Jest framework, а в магистральном - Jasmine. Достаточно сказать, что я уже был знаком с тестами javascript, но все еще недостаточно.

В нашем текущем проекте мне нужно было провести несколько модульных тестов, и, поскольку я в первую очередь Ruby-разработчик, я в основном просто переводил то, что я хотел бы сделать с Ruby, в Javascript. Если вы двуязычный (как я), то знаете, что иногда нет прямого перевода для некоторых слов, идиом и т. Д. То же самое и с языками программирования, особенно с языками с другими парадигмами.

В Ruby нашим обычным методом тестирования модульных тестов было просто заглушить службы, PORO и т. Д. С помощью моков. Вот один надуманный пример. Допустим, у вас есть класс, который отправляет запрос в API. Этот класс использует класс RouteBuilder следующим образом:

Чтобы проверить это в Ruby, мы обычно просто заглушаем RouteBuilder, чтобы можно было легко настроить такие сценарии:

Чтобы проверить, мы просто говорим, что в тесте всякий раз, когда вызывается RouteBuilder.new, он будет возвращать поддельный, который ведет себя по-разному в зависимости от контекста (в этом случае он либо выдает ошибку, либо возвращает действительный URL-адрес).

Сейчас, в стране Javascript, я делаю (или, по крайней мере) пытаюсь сделать то же самое. Это хорошо работает, если мы следуем шаблону нашей команды для PORO в Ruby, где мы всегда создаем экземпляры и объекты и имеем единственный метод, называемый run. Однако разница в том, что в Javascript вы можете просто экспортировать такую ​​функцию:

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

const routeBuilder = require('../lib/routeBuilder')
module.exports.run = (params, context, callback) => {
  const endpoint = routeBuilder.myUrl(params.uploader, 'update')
  ...
end

Я попробовал сделать то же самое, заглушив его:

routeBuilderMock = sinon.stub(routeBuilder, 'myUrl')
routeBuilderMock.withArgs('some-uploader').returns('/api/test')

Это действительно работает. Однако для некоторых функций, особенно чистых, это сложно сделать (по крайней мере, для меня), поскольку sinon не поддерживает это. Я думаю, это потому, что это запах (в функциональном смысле), поэтому я поспрашивал. Rico Sta. Круз предложил инъекцию зависимости , чтобы облегчить мне жизнь.

Теперь в Ruby внедрение зависимостей также является шаблоном, который мы используем. Обычно мы используем его для объектов, которые имеют разное поведение для каждой среды (постановка, производство и т. Д.). В функциональной парадигме это упрощает нашу жизнь, потому что теперь нам даже не нужно заглушать! Просто передайте поддельный объект / функцию или что-то еще, и пусть он ведет себя так, как мы хотим.

Давайте попробуем это сейчас, изменив функцию, которая использует наш построитель маршрутов:

const defaultRouteBuilder = require('../lib/routeBuilder')
module.exports.run = (params, context, callback) => {
  const routeBuilder = context.routeBuilder || defaultRouteBuilder
...
end

Легкая и быстрая смена! По сути, вместо того, чтобы просто требовать и использовать функцию построения маршрутов, мы теперь устанавливаем ее по умолчанию и позволяем вызывающей стороне передавать построитель маршрутов через аргумент context. При вызове это будет выглядеть так:

apiCaller.run('some-uploader', { routeBuilder: newRouteBuilder }, callback)

В нашем тесте это тоже несложно:

const routeBuilderMock = { dispatchUrl: () => '/api/test' }
const context = { routeBuilder: routeBuilderMock }
const result = apiCaller.run(params, context, callback)

Имейте в виду, что мы должны использовать соответствующий инструмент для нашей задачи. Хотя мне нравится, насколько легко было заглушить построитель маршрутов с помощью sinon или внедрения зависимостей, сделать это для всех наших функций может быть сложно (по той или иной причине). Кроме того, бывают случаи, когда я хочу также убедиться (в своих тестах), что построитель получает правильные параметры. Если бы я использовал внедрение зависимостей, было бы НАМНОГО больше шаблонов, чтобы проверить, что он должен получить some-uploader в качестве аргумента.

Кроме того, похоже, что у Jest уже есть доступ почти ко всем этим, поэтому в следующие пару недель я постараюсь включить его в наш набор тестов.

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