Не просто проверяйте свой оркестр, репетируйте в концертном зале.

Тестирование программного обеспечения имеет решающее значение.
То есть как для самого программного обеспечения, так и для бизнеса, который на него полагается.

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

Чего-чего?

Что я имею в виду и почему считаю это важным:

Производственные системы вряд ли состоят только из кода приложения. В подавляющем большинстве случаев использования приложения представляют собой композиции кода, который вы написали, процесс сборки для объединения / компиляции вашего кода с кодом поставщика, базами данных, CDN, очереди,…

Итак, почему важно заботиться не только о том, что вы создали?

Вы когда-нибудь смотрели репетицию концерта или чего-то подобного?
Они не только «тестируют» весь оркестр (~ integration-testing), но и каждое соло музыканта ( ~ компонентное тестирование). Конечно, убедитесь, что все инструменты работают (~ unit-testing).
Кроме того, они не только проводят все тестирование в своем месте, где они всегда встречаются и практикуются, но они действительно идут и репетируем в концертном зале. Потому что играть в среде, отличной от той, в которой вы тренируетесь, - это просто новинка!

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

Тогда давай репетируем!

Как вы решите провести последнюю репетицию, действительно зависит от вас, а также во многом зависит от вашего сценария использования.
Но я хотел бы показать вам, как я это делаю.

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

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

Предварительные требования
Вам потребуются следующие вещи:

  • Node.js
  • Докер
  • Terraform

Легго!

Для начала нам нужен каталог и файлы, с которыми мы будем работать:

# make directory
$ mkdir orchestra
$ cd $_
# init as npm package & install
$ npm init -y
$ npm i aws-sdk jest
# creating the files
$ touch \
write.js read.js \
orchestra.test.js \
main.tf

Хорошо, это голая рамка, над которой мы сейчас будем работать!

DynamoDB

Если вы не знакомы с тем, что на самом деле представляет собой DynamoDB, ознакомьтесь с документацией AWS.

Нам нужна только самая простая таблица с одним ключом:

// main.tf
provider "aws" {
    skip_credentials_validation = true
    skip_metadata_api_check     = true
    access_key                  = "mock_access_key"
    secret_key                  = "mock_secret_key"
    region                      = "us-east-1"
    endpoints {
        dynamodb = "http://localhost:4569"
    }
}
resource "aws_dynamodb_table" "orchestra_db" {
  name           = "Orchestra"
  billing_mode   = "PROVISIONED"
  read_capacity  = 2
  write_capacity = 2
  hash_key       = "sampleId"
attribute {
    name = "sampleId"
    type = "S"
  }
ttl {
    attribute_name = "TimeToExist"
    enabled        = false
  }
}

Затем нам также необходимо запустить поставщиков терраформ и применить нашу «мини-инфраструктуру».

# first start a LocalStack Docker container
$ docker run -d \
   -p 4567-4578:4567-4578 -p 8080:8080 \
   localstack/localstack
$ terraform init
$ terraform apply --auto-approve
# if you had the aws CLI installed
# you could verify that the table was created
aws dynamodb list-tables \
--endpoint-url http://localhost:4569

Материал JavaScript

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

// write.js
const AWS = require('aws-sdk');
const { promisify } = require('util');
// we need to explicitly set the endpoin for local usage
const dynamodb = new AWS.DynamoDB({
  apiVersion: '2012-08-10',
  endpoint: 'http://localhost:4569',
  region: 'us-east-1'
});
// i like working with promises
// so i make dynamo return promises
dynamodb.putItem = promisify(dynamodb.putItem);
async function writeToDb(sampleId) {
  const dynamoParams = {
    TableName: 'Orchestra',
    Item: {
      sampleId: {
        S: sampleId
      }
    }
  };
  return dynamodb.putItem(dynamoParams);
}
module.exports = { writeToDb };

Это позволит нам записать в таблицу произвольную запись с sampleId.

Давайте создадим обратную операцию записи. Прочтите.
Это в основном код для записи.

// read.js
const AWS = require('aws-sdk');
const { promisify } = require('util');
// we need to explicitly set the endpoin for local usage
const dynamodb = new AWS.DynamoDB({
  apiVersion: '2012-08-10',
  endpoint: 'http://localhost:4569',
  region: 'us-east-1'
});
// i like working with promises
// so i make dynamo return promises
dynamodb.getItem = promisify(dynamodb.getItem);
async function readFromDb(sampleId) {
  const dynamoParams = {
    TableName: 'Orchestra',
    Key: {
      sampleId: {
        S: sampleId
      }
    }
  };
  return dynamodb.getItem(dynamoParams);
}
module.exports = { readFromDb };

Тестируем это

На данный момент у нас есть приложение, которое, как мы полагаем, работает.
Чтобы все работало должным образом, мы должны его протестировать:

// orchestra.test.js
const { writeToDb } = require('./write');
const { readFromDb } = require('./read');
const { exec } = require('child_process');
// you may want to set the terraform executable by hand
const tfExec = process.env.TF_EXEC || 'terraform';
jest.setTimeout(30000);
// create infrastructure we need for the application
beforeAll(() => {
  return new Promise(resolve => {
    exec(`${tfExec} apply --auto-approve`, (err, so, se) => {
      console.log('ERROR: ', err);
      console.log('STDOUT: ', so);
      console.log('STDERR: ', se);
      resolve();
    });
  });
});
// clean up and destroy the infrastructure
afterAll(() => {
  return new Promise(resolve => {
    exec(`${tfExec} destroy --force`, (err, so, se) => {
      console.log('ERROR: ', err);
      console.log('STDOUT: ', so);
      console.log('STDERR: ', se);
      resolve();
    });
  });
});
describe('Testing our application', () => {
  // unit testing insertion
  test('writeToDb function', async () => {
    const dbRes = await writeToDb('mySampleValue');
    expect(dbRes).toEqual({});
  });
  
  // unit testing reading
  test('readFromDb function', async () => {
    const dbRes = await readFromDb('unknown');
    expect(dbRes).toEqual({});
  });
  // actually testing our orchestra
  // the way its going to work in prod
  test('things hand in hand', async () => {
    const sampleKey = 'orchestra';
    await writeToDb(sampleKey);
    const { Item: { sampleId } } = await readFromDb(sampleKey);
    expect(sampleId.S).toBe(sampleKey);
  });
});

Пусть начнутся тесты !!
(Не путайте, тестирование займет некоторое время, так как мы заранее инициализируем DynamoDB, а потом отключаем его)

$ jest

Вуаля! Теперь мы можем быть уверены, что наше приложение работает.
Потому что мы репетируем в концертном зале.

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

А пока желаю удачного кодирования!

Бонус

Вы ведь не только запускаете тесты локально, не так ли?
Я тоже.
Поэтому я всегда проверяю, работают ли мои тесты и в CI / CD.

Тогда давайте настроим Travis:
Примечание. если вы хотите использовать Travis, вам понадобится репозиторий Github и в качестве источника укажите ваш каталог

$ touch .travis.yml
# .travis.yml
# define the language used
language: node_js
node_js:
- stable
# docker as service for localstack
services:
  - docker
# manually install terraform CLI
before_install:
  - wget https://releases.hashicorp.com/terraform/0.11.3/terraform_0.11.3_linux_amd64.zip
  - unzip terraform_0.11.3_linux_amd64.zip
# export access keys for terraform
# since we use it only locally, the keys can be nonsense
# also we start the localstack image
# init terraform
# start npm test
script:
  - export AWS_ACCESS_KEY_ID="ANYKEY"
  - export AWS_SECRET_ACCESS_KEY="ANYSECRET"
  - docker run -d -p 4567-4578:4567-4578 -p 8080:8080 localstack/localstack
  - ./terraform init
  - TF_EXEC=./terraform npm t

Начинаем!
Благодаря тому, что все наши тесты проходят и на TravisCI, мы можем быть намного более уверены в том, что то, что мы загружаем и объединяем, действительно безопасно для запуска в продакшене!