Недавно мы опробовали 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 (особенно тот, который имеет проверку аргументов), если вы просто хотите высмеять что-то независимо от аргумента, тогда внедрение зависимостей сделает работу за вас.