Как структурировать лямбда-код для тестируемости

#javascript #node.js #amazon-web-services #aws-lambda

#javascript #node.js #amazon-веб-сервисы #aws-lambda

Вопрос:

Я пытаюсь создать небольшой REST API с API gateway, lambda и DynamoDB, следуя при этом хорошим практикам разработки, таким как TDD. Я привык к возможности использования контейнера DI для подготовки моих объектов, который идеально подходит для макетирования и тестирования. Во фреймворке MVC была бы единственная точка входа, где я мог бы определить конфигурацию моего контейнера, выполнить загрузку приложения и вызвать контроллер для обработки события. Я мог бы протестировать контроллер независимо от остальной части приложения и внедрить поддельные зависимости. Я не могу понять, как отделить зависимости, которые может иметь лямбда-функция, от самой лямбда-функции. Например:

 const { DynamoDB } = require('aws-sdk')
const { UserRepo } = require('../lib/user-repo')

const client   = new DynamoDB({ region: process.env.REGION }) // Should be resolved by DI container
const userRepo = new UserRepo(client) // Should be resolved by DI container

exports.handler = async (event) => {
  return userRepo.get(event.id)
}
  

Пожалуйста, кто-нибудь может направить меня в правильном направлении для структурирования лямбда-кода, чтобы его можно было правильно протестировать?

Ответ №1:

Один из способов, которым мы подошли к этому в проекте, над которым я сейчас работаю, — это разделение требований, поэтому handler отвечает за:

  • Создание клиентов;
  • Извлечение любой конфигурации из среды; и
  • Получение параметров из события.

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

В вашем конкретном случае это может выглядеть следующим образом:

 const { DynamoDB } = require('aws-sdk');
const { UserRepo } = require('../lib/user-repo');

const doTheWork = (repo, id) => repo.get(id);

exports.handler = async (event) => {
  const client = new DynamoDB({ region: process.env.REGION });
  const userRepo = new UserRepo(client); 
  return doTheWork(userRepo, event.id);
}
  

doTheWork теперь это можно выполнять на уровне модуля, используя тестовые дубли для объекта repo и любых входных данных, которые вы хотите. UserRepo Уже разделен путем внедрения конструктора в клиент Dynamo, так что это тоже должно быть довольно легко тестируемо.

У нас также есть тесты на уровне интеграции, которые только имитируют содержимое AWS SDK (в качестве альтернативы вы могли бы использовать макет транспортного уровня или что-то вроде aws-sdk-mock ) плюс тестирование E2E, которое гарантирует совместную работу всей системы.

Комментарии:

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

2. @BenGuest да, мы выбрали именно эту модель.