Контейнеры в целом и Docker-контейнеры в частности немного изменили наше представление о развертывании и распространении программного обеспечения. Запуск приложения в контейнере, а не прямо на вашем компьютере или сервере, имеет много преимуществ. Но что насчет тестирования и отладки? Сможем ли мы также отлаживать приложение в контейнере, как если бы оно было установлено на машине? В этой статье рассказывается, как настроить приложение и среду для тестирования Node-контейнеров. Исходный код предложенного проекта можно найти GitHub.
Создание простого приложения
Примечание В качестве примера рассматривается простое Node-приложение. Представленные здесь принципы могут быть легко адаптированы к другим языкам программирования, например, Python, Go или .NET Core.
Создаем директорию проекта и в терминале перемещаемся в нее. Выполняем команду:
npm init
На вопрос о точке входа (entry point) вводим server.js
и на вопрос о тестовой команде (test command) отвечаем jasmine-node spec
. Остальные вопросы можно пропустить, оставив значение по умолчанию. По завершении работы этой команды будет создан файл package.json
, который должен выглядеть примерно так:
{
"name": "debug-node",
"version": "1.0.0",
"description": "",
"main": "server.js",
"scripts": {
"test": "jasmine-node spec",
"start": "node server.js"
},
"author": "Gabriel Schenker",
"license": "ISC",
"dependencies": {
}
}
Наше приложение будет использовать Express JS. Установим пакет командой:
npm install express --save
Теперь создаем файл server.js
, который будет содержать следующий код:
var express = require('express');
var app = express();
var port = 3000;
var primes = require('./primes.js');
app.get('/', function(req, res){
res.status(200).send('Testing and Debugging Sample');
})
app.get('/isPrime/:number', function(req, res){
res.status(200).send(primes.isPrime(req.params.number));
})
exports.stop = function(){
server.close();
}
var server = app.listen(port, function(){
console.log("Express server listening on port %d in %s mode", port, app.settings.env);
});
В строке 4 мы импортируем модуль primes.js, который содержит логику, позволяющую определить, является ли данное число простым. Таким образом, нужно добавить в проект файл primes.js со следующим содержимым:
exports.isPrime = function(number){
for(var i = 2; i < number/2; i++) {
if(number % i === 0) {
return false;
}
}
return number > 1;
}
Добавляем Dockerfile
Чтобы иметь возможность упаковать наше приложение в Docker-контейнер, нам нужно добавить Dockerfile
в проект. Содержимое этого файла должно выглядеть так:
FROM node:4.2.3
# для тестирования
RUN npm install -g jasmine-node
RUN mkdir /app
WORKDIR /app
COPY package.json /app/
RUN npm install
COPY . /app
EXPOSE 3000 5858
ENTRYPOINT ["npm", "start"]
Обратите внимание, что в строке 3 мы устанавливаем jasmine-node
, который нужен для запуска тестов. Также мы открываем не только порт 3000, но и порт 5858. Последний будет использоваться для присоединения отладчика. Также обратите внимание на то, что мы сначала копируем package.json
в образ и запускаем npm install
, и только после этого копируем оставшуюся часть папки приложения. Это помогает нам оптимизировать сборку Docker-образа.
Запуск приложения в контейнере
Теперь давайте соберем Docker-образ нашего приложения. Выполним команду:
docker build -t my-app .
Примечание Не забудьте указать точку в конце команды.
Теперь мы можем получить контейнер из этого образа:
docker run -d --name my-app -p 3000:3000 my-app
Мы должны увидеть длинный ID (хеш-код), который выводится в терминал. Чтобы повторно убедиться, работает ли контейнер, мы можем использовать команду docker ps
, и должны увидеть следующее:
Если по какой-либо причине контейнер не создался, мы можем просмотреть логи создания командой docker logs my-app
:
Добавляем тесты
Для тестирования мы будем использовать Jasmine. Мы можем поставить его глобально на нашей машине, но зачем это делать, если у нас есть контейнер? В данной статье продемонстрирована глобальная установка Jasmine, но вы можете самостоятельно установить Jasmine в контейнер. Давайте установим:
npm install -g jasmine-node
Теперь настроим несложный тест. Создайте папку spec
и добавьте в нее файл primes-spec.js
со следующим содержимым:
var sut = require('../primes.js');
describe("when evaluating if a given number is a prime", function(){
describe("when the number is a prime", function(){
it("should return 'true'", function(){
expect(sut.isPrime(11)).toBe(true);
});
});
});
Здесь не будет описана логика Jasmine. Вы можете обратиться к подробной документации, чтобы разобраться с этим.
Как только мы определили тест, выполним эту команду в терминале:
npm test
Эта команда вызывает Jasmine-node
, который мы определили в package.json
. Вывод будет примерно такой:
Также мы можем добавлять новые тесты в приложение. Для этого нам нужно добавить модуль require
:
npm install request --save
Теперь добавьте файл с именем server-spec.js
в папку spec
. Он должен иметь такое содержание:
var request = require("request");
var server = require("../server.js");
var base_url = "http://localhost:3000/";
describe("when testing projects endpoint", function(){
it("should return status OK", function(done){
request.get(base_url+"isPrime/11", function(error, response, body) {
expect(response.statusCode).toBe(200);
done();
});
});
it("should return list of projects", function(done){
request.get(base_url+'isPrime/11', function(error, response, body) {
expect(JSON.parse(response.body)).toBe(true);
done();
})
});
// Остановка сервера
it('should clean up', function(){
server.stop();
})
})
Теперь мы можем снова запустить все тесты: npm test
.
Давайте запустим тест в контейнере. Для этого используется docker-compose
. Добавим файл docker-compose.test.yml
в проект:
sut:
build: .
ports:
- "3000:3000"
entrypoint: jasmine-node spec
Этот yaml-файл содержит инструкции по созданию образа контейнера с использованием Dockerfile, открытии порта 3000 и сопоставлении его с портом 3000 на хосте. Наконец, переопределяем значение entrypoint
. Теперь в терминале введите следующую команду:
docker-compose -f docker-compose.test.yml up --build
Это создаст образ Docker и запустит контейнер с использованием этого образа с переопределенной точкой входа. Если все работает правильно, мы должны увидеть такой вывод в терминале:
Здесь создается Docker-контейнер и выполняются все тесты. Тесты, которые прошли успешно, возвращают код 0
. Мы можем остановить тестирование командой:
docker-compose -f docker-compose.test.yml down
Отладка приложения
Мы научились выполнять автоматические тесты приложений. Но что насчет отладки? Ниже будет показано, как проходить код по строкам и проверять значения переменных.
Примечание В данном примере используется Visual Studio Code. Но эти действия можно совершить в большинстве современных редакторов.
Для начала нам нужно узнать, на каком IP-адресе находится хост Docker. Выполним команду:
docker-machine ip default
Добавим в проект файл docker-compose.debug.yml
со следующим содержимым:
sut:
build: .
ports:
- "3000:3000"
- "5858:5858"
entrypoint: node --debug=5858 server.js
Подобно файлу docker-compose
для тестирования, мы используем Dockerfile
для создания образа, открываем порт 3000 и порт 5858, сопоставляем их с теми же портами на хосте. Наконец, мы переопределяем точку входа node --debug = 5858 server.js
. То есть запускаем узел в режиме отладки, прослушивая порт 5858. Мы можем начать сеанс отладки, используя эту команду:
docker-compose -f docker-compose.debug.yml up --build -d
В качестве последнего шага нам необходимо настроить Visual Studio Code для присоединения к Node-приложению, запущенному в контейнере. Добавим папку .vscode
в наш проект. Внутри этой папки мы добавляем файл launch.json
.
Содержимое файла должно быть следующим:
{
"version": "0.1.0",
// Список настроек.
"configurations": [
{
// Имя параметра.
"name": "Launch server.js",
// Тип Конфигурации.
"type": "node",
// Путь к приложению.
"program": "server.js",
// Автоматическая остановка программы.
"stopOnEntry": false,
// Аргументы командной строки.
"args": [],
// Путь к директории проекта.
"cwd": ".",
"runtimeExecutable": null,
"runtimeArgs": ["--nolazy"],
// Переменные среды используемые в проекте.
"env": {
"NODE_ENV": "development"
},
"sourceMaps": false,
"outDir": null
},
{
"name": "Attach",
"type": "node",
"request": "attach",
"port": 5858,
"address": "192.168.99.100",
"restart": false,
"sourceMaps": false,
"outDir": null,
"localRoot": "${workspaceRoot}/",
"remoteRoot": "/app/"
}
]
}
Обратите внимание на раздел с именем Attach
. Вы должны убедиться, что в поле address
командой docker-machine ip default
указан IP-адрес вашего Docker-хоста.
Как только была добавлена конфигурация запуска, мы можем щелкнуть по кнопке отладки. Убедитесь, что выбрана настройка Attach, а затем нажмите кнопку запуска.
Теперь добавьте точку останова в строку 7 файла server.js
. Откройте браузер и перейдите к 192.168.99.100:3000 (замените IP-адрес своим, если у вас другой). Отладчик должен остановиться в строке 7:
В окне отладки мы видим всю отладочную информацию:
Итог
Мы научились тестировать и дебажить приложения внутри Docker-контейнера. Преимущество этого метода в том, что нам не нужно загрязнять компьютер библиотеками и фреймворками для поддержки тестирования и отладки.
Перевод статьи «Testing and Debugging a Containerized Node application»