}
// (...)
}
Обратите внимание на новый вспомогательный метод из пакета random, входящего в состав Terratest. Вам нужно еще раз выполнить depensure. Этот код присваивает переменной alb_name значение test-
Аналогично модифицируйте пример Hello, World. Для начала добавьте новую входную переменную в файл examples/hello-world-app/variables.tf:
variable "environment" {
description = "The name of the environment we're deploying to"
type = string
default = "example"
}
Затем передайте эту переменную модулю hello-world-app:
module "hello_world_app" {
source = "../../../modules/services/hello-world-app"
server_text = "Hello, World"
environment = var.environment
mysql_config = var.mysql_config
instance_type = "t2.micro"
min_size = 2
max_size = 2
enable_autoscaling = false
}
И присвойте переменной environment в файле test/hello_world_app_example_test.go значение, которое включает в себя random.UniqueId():
func TestHelloWorldAppExample(t *testing.T) {
t.Parallel()
opts := &terraform.Options{
// Сделайте так, чтобы этот относительный путь вел
// к папке с примерами для hello-world-app!
TerraformDir: "../examples/hello-world-app/standalone",
Vars: map[string]interface{}{
"mysql_config": map[string]interface{}{
"address": "mock-value-for-test",
"port": 3306,
},
"environment": fmt.Sprintf("test-%s", random.UniqueId()),
},
}
// (...)
}
После внесения этих изменений параллельный запуск ваших тестов должен быть безопасным:
$ go test -v -timeout 30m
TestAlbExample 2019-05-26T17:57:21+01:00 (...)
TestHelloWorldAppExample 2019-05-26T17:57:21+01:00 (...)
TestAlbExample 2019-05-26T17:57:21+01:00 (...)
TestHelloWorldAppExample 2019-05-26T17:57:21+01:00 (...)
TestHelloWorldAppExample 2019-05-26T17:57:21+01:00 (...)
(...)
PASS
ok terraform-up-and-running 216.090s
Вы должны увидеть, что оба теста выполняются одновременно. Таким образом, общее время тестирования должно занимать примерно столько времени, сколько нужно для завершения самого медленного теста в вашем наборе (а не совокупное время работы всех тестов, как в случае с последовательным запуском).
Параллельное выполнение тестов в одной и той же папке
Еще один аспект параллелизма, который следует учитывать, — ситуация, когда несколько автоматических тестов запускаются для одной и той же папки Terraform. Например, вы могли бы запустить несколько разных тестов для examples/hello-world-app, каждый из которых перед выполнением terraform apply присваивает свои собственные значения входным переменным. Если вы попробуете это сделать, возникнет проблема: ваши тесты начнут конфликтовать, поскольку все они выполняют terraform init и тем самым переопределяют файлы состояния Terraform в папке .terraform.
Если вы хотите параллельно запускать несколько тестов для одной и той же папки, самое простое решение — сделать так, чтобы каждый тест копировал состояние в уникальную папку и запускал Terraform из нее, чтобы избежать конфликтов. У Terratest для этого есть встроенный вспомогательный метод, который даже следит за корректным размещением относительных путей для модулей Terraform: подробности ищите в документации к методу test_structure.CopyTerraformFolderToTemp.
Интеграционные тесты
Подготовив несколько модульных тестов, мы можем переходить к модульному тестированию. Чтобы сформировать общее понимание, которое позже можно будет применить к коду Terraform, лучше начать с примера веб-сервера, написанного на Ruby. Для интеграционного тестирования такого сервера нужно выполнить следующее.
1. Запустить веб-сервер на локальном компьютере так, чтобы он прослушивал порт.
2. Отправить ему HTTP-запросы.
Создадим в файле web-server-test.rb метод, который выполняет эти шаги:
def do_integration_test(path, check_response)
port = 8000
server = WEBrick::HTTPServer.new :Port => port
server.mount '/', WebServer
begin
# Запускаем веб-сервер в отдельном потоке, чтобы он не блокировал тест
thread = Thread.new do
server.start
end
# Отправляем HTTP-запрос по определенному пути веб-сервера
uri = URI("http://localhost:#{port}#{path}")
response = Net::HTTP.get_response(uri)
# Используем для проверки ответа заданную лямбда-функцию check_response
check_response.call(response)
ensure
# В конце теста останавливаем сервер и поток
server.shutdown
thread.join
end
end
Метод do_integration_test конфигурирует веб-сервер на порте 8000, запускает его в фоновом потоке (чтобы он не блокировал выполнение теста), шлет HTTP-запрос типа GET по заданному пути, передает HTTP-ответ на проверку функции check_response и в конце останавливает веб-сервер. Вот как с помощью этого метода можно написать интеграционный тест для точки входа /:
def test_integration_hello
do_integration_test('/', lambda { |response|
assert_equal(200, response.code.to_i)
assert_equal('text/plain', response['Content-Type'])
assert_equal('Hello, World', response.body)
})
end
Этот код вызывает метод do_integration_test с путем / и передает ему лямбду (встраиваемую функцию), которая проверяет, равны ли код и тело ответа 200OK и соответственно Hello,World. Похожи интеграционные тесты и для других точек входа, хотя для /web-service выполняется более общая проверка (то есть assert_include вместо assert_equal), чтобы минимизировать потенциальные проблемы при изменении точки входа example.org:
def test_integration_api
do_integration_test('/api', lambda { |response|
assert_equal(201, response.code.to_i)
assert_equal('application/json', response['Content-Type'])
assert_equal('{"foo":"bar"}', response.body)
})
end
def test_integration_404
do_integration_test('/invalid-path', lambda { |response|
assert_equal(404, response.code.to_i)
assert_equal('text/plain', response['Content-Type'])
assert_equal('Not Found', response.body)
})
end
def test_integration_web_service
do_integration_test('/web-service', lambda { |response|
assert_equal(200, response.code.to_i)
assert_include(response['Content-Type'], 'text/html')
assert_include(response.body, 'Example Domain')
})
end
Запустим все наши тесты:
$ ruby web-server-test.rb
(...)
Finished in 0.221561 seconds.
--------------------------------------------
8 tests, 24 assertions, 0 failures, 0 errors
100% passed
--------------------------------------------
Обратите внимание, что при модульном тестировании на выполнение всех тестов уходило 0,000572 секунды. Теперь же это время выросло примерно в 387 раз, до 0,221561 секунды. Конечно, выполнение по-прежнему происходит молниеносно, но это связано лишь с тем, что код веб-сервера на Ruby мало что умеет. Этот пример специально сделан как можно более компактным. Здесь важны не абсолютные показатели, а относительная тенденция: интеграционные тесты обычно работают медленней, чем модульные. Позже мы еще к этому вернемся.
Теперь сосредоточимся на интеграционном тестировании кода Terraform. Если раньше мы тестировали отдельные модули, то теперь нужно проверить, как они работают вместе. Для этого мы должны развернуть их одновременно и убедиться в том, что они ведут себя корректно. Ранее мы развернули демонстрационное приложение Hello, World с фиктивными данными вместо настоящей БД MySQL. Теперь развернем реальный модуль MySQL и посмотрим, как с ним интегрируется приложение Hello, World. У вас уже должен быть подходящий код в папках live/stage/data-stores/mysql и live/stage/services/hello-world-app. Таким образом, вы можете создать интеграционный тест для своей среды финального тестирования (точнее, для ее части).
Конечно, как уже упоминалось в этой главе, все автоматические тесты должны выполняться в изолированной учетной записи AWS. Поэтому при проверке кода для среды финального тестирования все тесты следует запускать от имени изолированного тестового пользователя. Если в ваших модулях есть код, который специально прописан для среды финального тестирования, самое время сделать его конфигурируемым, чтобы вы могли внедрять тестовые значения. Позвольте менять в файле live/stage/data-stores/mysql/variables.tf название базы данных, используя новую входную переменную db_name:
variable "db_name" {
description = "The name to use for the database"
type = string
default = "example_database_stage"
}
Передайте это значение модулю mysql в файле live/stage/data-stores/mysql/main.tf:
module "mysql" {
source = "../../../../modules/data-stores/mysql"