모킹
모킹 (Mocks)
참고: 테스트 모킹은 Terraform v1.7.0 이상에서 사용할 수 있어요.
출처: 문서
본문
Terraform은 테스트를 위해 프로바이더, 리소스, 데이터 소스를 모킹할 수 있게 해줘요. 이렇게 하면 인프라를 만들거나 자격 증명이 필요하지 않고도 모듈의 일부를 테스트할 수 있어요. Terraform 테스트에서 모킹된 프로바이더나 리소스는 기본 프로바이더 API가 일반적으로 제공하는 모든 계산된(computed) 속성에 대해 가짜(fake) 데이터를 생성해요.
모킹 기능은 terraform test 언어에서만 사용할 수 있어요. 이 문서의 독자는 테스트 문법과 언어 기능에 익숙해야 해요. 또한 모킹 프레임워크의 더 고급 기능은 다음 Terraform 프로바이더 기능에 대한 지식이 필요해요:
- 속성, 중첩 속성, 블록의 차이.
- 선택적, 필수, 계산된 속성의 정의.
모킹 프로바이더 (Mock Providers)
Terraform 테스트에서 mock_provider 블록으로 프로바이더를 모킹할 수 있어요. 모킹 프로바이더는 원래 프로바이더와 같은 스키마를 반환하며, 일치하는 프로바이더 대신 테스트에 전달할 수 있어요. 모킹 프로바이더가 검색한 모든 리소스와 데이터 소스는 구성에서 관련 값을 설정하고, 계산된 속성에 대해 가짜 데이터를 생성해요.
모킹 프로바이더는 전통적인 provider 블록을 대신해 직접 사용할 수 있고 같은 전역 네임스페이스를 공유해요. terraform test 명령의 실행 동안 Terraform은 실제 프로바이더와 모킹된 프로바이더를 구분하지 않아요.
다음 예시는 AWS S3 버킷을 만들고 모킹된 프로바이더를 사용해 AWS 계정 없이 구성을 테스트해요.
# main.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
}
}
}
variable "bucket_name" {
type = string
}
resource "aws_s3_bucket" "my_bucket" {
bucket = var.bucket_name
}
# bucket_name.tftest.hcl
mock_provider "aws" {}
run "sets_correct_name" {
variables {
bucket_name = "my-bucket-name"
}
assert {
condition = aws_s3_bucket.my_bucket.bucket == "my-bucket-name"
error_message = "incorrect bucket name"
}
}
Terraform 테스트 파일에서 실행되는 plan 또는 apply 작업의 관점에서 보면, 모킹된 프로바이더는 구성과 일치하는 값을 가진 실제 리소스를 만드는 것이에요. 이러한 리소스는 terraform test가 테스트 실행 중에 만들고 메모리에 보관하는 Terraform 상태 파일에 저장돼요.
Terraform 테스트에서 모킹 프로바이더와 실제 프로바이더를 함께 사용할 수 있어요. 다음 예시는 하나는 실제, 하나는 모킹된 두 개의 AWS 프로바이더를 정의해요. 두 프로바이더가 같은 전역 aws 프로바이더 네임스페이스를 공유하므로 그중 하나에 별칭(alias)을 제공해야 해요. 그런 다음 테스트 run 블록의 providers 속성을 사용해 각 run 블록에 사용할 AWS 프로바이더를 커스터마이즈할 수 있어요.
# mocked_providers.tftest.hcl
provider "aws" {}
mock_provider "aws" {
alias = "fake"
}
run "use_real_provider" {
providers = {
aws = aws
}
}
run "use_mocked_provider" {
providers = {
aws = aws.fake
}
}
생성된 데이터 (Generated data)
모킹된 프로바이더는 참조된 데이터 소스나 속성에서 계산된 속성에 대한 데이터를 생성해요. 예를 들어 arn 속성은 AWS가 대부분의 리소스에 대해 생성하는 고유 식별자예요. 모킹된 aws 프로바이더는 만드는 모든 리소스에서 이 속성에 대한 값을 제공해요.
참고: 모킹 프로바이더는 계산된 속성의 예상 형식에 대한 정보가 없으므로, 생성된 데이터는 실제 프로바이더가 그 속성에 대해 반환할 예상 문법과 거의 일치하지 않아요.
모킹 프로바이더는 계산된 속성에 대해서만 데이터를 생성해요. 모킹 프로바이더를 사용할 때는 필수 리소스 속성을 모두 설정해야 해요. 선택적 계산 속성에 값을 제공하지 않으면 Terraform이 자동으로 하나를 생성해요. Terraform이 생성하는 값은 데이터 타입에 따라 달라요:
- 숫자는 0이 돼요.
- 불리언은 false가 돼요.
- 문자열은 무작위 8자 영숫자 문자열이 돼요.
- 집합, 리스트, 맵을 포함한 컬렉션은 빈 컬렉션이 돼요.
- 객체는 이 같은 규칙 집합을 사용해 재귀적으로 생성된 모든 필수 하위 속성을 포함해요.
이것의 예는 aws_s3_bucket 리소스의 bucket 속성이에요. 실제 AWS 프로바이더는 버킷 이름이 지정되지 않으면 버킷 이름을 생성해요. 모킹된 AWS 프로바이더도 마찬가지로, 구성에 이미 지정되지 않은 경우에만 값을 생성해요.
기본적으로 Terraform은 apply 작업 동안 데이터를 생성하고 plan 작업 동안 (known after apply) 값을 반환해요. mock_provider 블록의 override_during 속성으로 이 동작을 재정의할 수 있어요:
mock_provider "aws" {
override_during = plan
}
위 aws 프로바이더는 plan 작업 동안 데이터를 생성하고 apply 작업 동안 같은 데이터를 재사용해요. override_during 속성은 plan 또는 apply 값을 받아들여요.
모킹 프로바이더 데이터 (Mock Provider data)
대상 리소스와 데이터 소스에 대해 특정 값을 지정할 수 있어요. mock_provider 블록에서 원하는 만큼 mock_resource와 mock_data 블록을 작성할 수 있어요. mock_resource와 mock_data 블록 둘 다 값을 제공하려는 리소스나 데이터 소스와 일치해야 하는 type 인자를 받아들여요. 또한 특정 속성에 대해 반환할 값을 지정하는 데 사용할 수 있는 defaults 객체 속성도 받아들여요.
다음 예시는 모든 AWS S3 버킷 리소스와 데이터 소스에 대해 설정된 arn 값을 제공하는 방법을 보여줘요:
mock_provider "aws" {
mock_resource "aws_s3_bucket" {
defaults = {
arn = "arn:aws:s3:::name"
}
}
mock_data "aws_s3_bucket" {
defaults = {
arn = "arn:aws:s3:::name"
}
}
}
위 예시에서 Terraform은 무작위 문자열을 생성하는 대신 S3 버킷의 arn 속성에 대해 제공된 값을 사용해요. 명시적 기본값이 제공되지 않은 계산된 속성은 단순히 일반적인 데이터 생성 규칙으로 돌아가요. 또한 mock_resource와 mock_data 블록에서 override_during 속성을 사용해 Terraform이 개별 리소스에 대한 값을 언제 생성할지 지정할 수 있어요. override_during 속성을 지정하지 않으면 Terraform은 mock_provider 블록에서 상속된 규칙을 사용해 값을 생성해요. 지정하면 로컬 값이 mock_provider 블록에 지정된 어떤 값도 재정의해요.
전용 모킹 데이터 파일을 작성하고 mock_provider 블록의 source 속성을 사용해 테스트 간에 모킹 프로바이더 데이터를 공유할 수도 있어요. 모킹 데이터 파일은 .tfmock.hcl 또는 .tfmock.json 확장자를 가지며, mock_provider 블록 안에 직접 정의된 것처럼 mock_resource와 mock_data 블록을 포함할 수 있어요.
# ./testing/aws/data.tfmock.hcl
mock_resource "aws_s3_bucket" {
defaults = {
arn = "arn:aws:s3:::name"
}
}
mock_data "aws_s3_bucket" {
defaults = {
arn = "arn:aws:s3:::name"
}
}
mock_provider "aws" {
source = "./testing/aws"
}
위 예시는 ./testing/aws에 있는 모킹 데이터 파일에서 mock_resource와 mock_data 블록을 정의해요. 여러 테스트 파일에 정의를 복사하지 않고 여러 테스트 파일을 로드해서 같은 모킹 프로바이더 데이터를 공유할 수 있어요.
source 속성을 직접 중첩된 mock_resource와 mock_data 블록과 결합할 수 있어요. 소스 위치와 직접 중첩된 블록이 같은 리소스나 데이터 소스를 설명하면 직접 중첩된 블록이 우선해요.
재정의 (Overrides)
프로바이더를 모킹하는 것 외에도 다음 블록 타입으로 특정 리소스, 데이터 소스, 모듈을 재정의할 수 있어요:
override_resource: 리소스의 값을 재정의해요. Terraform은 기본 프로바이더를 호출하지 않아요.override_data: 데이터 소스의 값을 재정의해요. Terraform은 기본 프로바이더를 호출하지 않아요.override_module: 모듈의 출력을 재정의해요. Terraform은 모듈 안의 어떤 리소스도 만들지 않아요.
세 블록 모두 Terraform 테스트 파일의 루트 레벨과 Terraform 테스트 파일 run 블록에 배치할 수 있어요. 또한 override_resource와 override_data 블록은 mock_provider 블록과 Terraform 모킹 데이터 파일에 중첩될 수 있어요.
재정의는 실제 프로바이더와 모킹된 프로바이더 모두와 함께 사용할 수 있으며, 기본 프로바이더 대신 계산된 값을 제공해요.
재정의 문법 (Overrides Syntax)
모든 재정의 블록은 재정의할 리소스, 데이터 소스, 모듈을 지정해야 하는 target 속성을 포함해요. override_module 블록은 outputs 속성을, override_resource와 override_data 블록은 values 속성을 포함해요. 재정의 블록은 또한 override_during 속성을 지원해요.
outputs와 values 속성은 선택 사항이며, 지정하지 않으면 Terraform이 값을 자동으로 생성해요. override_during 속성도 선택 사항이며, 지정하면 mock_provider 블록의 override_during 속성을 재정의해요. 지정하지 않으면 Terraform은 mock_provider 블록의 동작을 상속해요.
다음 예시는 다양한 범위와 레벨에서의 재정의 블록을 보여줘요. 주 구성은 ./modules/s3_data 모듈을 호출해 S3 버킷에서 파일을 읽은 다음, 모듈에서 반환된 데이터로 local_file을 만들어요.
# main.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
}
}
}
module "credentials" {
source = "./modules/s3_data"
data_bucket_name = "my_company_bucket_name"
}
resource "local_file" "credentials_json" {
filename = "credentials.json"
content = jsonencode(module.credentials.data)
}
# ./modules/s3_data/main.tf
variable "data_bucket_name" {
type = string
}
data "aws_s3_object" "data_bucket" {
bucket = var.data_bucket_name
key = "credentials.json"
}
output "data" {
value = jsondecode(data.aws_s3_object.data_bucket.body)
}
첫째, 모킹 프로바이더 안에서 모듈의 aws_s3_bucket_object를 직접 재정의할 수 있어요. 다음 예시는 mock_provider 블록 안에 override_data 블록을 정의해요. 이 경우 Terraform은 모킹 프로바이더가 대상 데이터 소스를 만들 때만 그 데이터 소스를 재정의해요.
# main.tftest.hcl
mock_provider "aws" {
override_data {
target = module.credentials.data.aws_s3_object.data_bucket
values = {
body = "{\"username\":\"username\",\"password\":\"password\"}"
}
}
}
run "test" {
assert {
condition = jsondecode(local_file.credentials_json.content).username == "username"
error_message = "incorrect username"
}
}
대안으로 테스트 파일 자체나 run 블록에서 같은 aws_s3_bucket_object를 재정의할 수도 있어요. 이 경우 Terraform이 프로바이더와 무관하게 대상 주소에서 데이터 소스를 재정의하므로 기본 프로바이더는 중요하지 않아요. 다음 예시는 여전히 프로바이더를 모킹해서 Terraform이 AWS 자격 증명 없이 테스트를 실행할 수 있게 해요. 재정의 블록은 실제 프로바이더가 제공하는 리소스를 재정의할 수 있어요.
# main.tftest.hcl
mock_provider "aws" {}
override_data {
target = module.credentials.data.aws_s3_object.data_bucket
values = {
body = "{\"username\":\"username\",\"password\":\"password\"}"
}
}
run "test_file_override" {
assert {
condition = jsondecode(local_file.credentials_json.content).username == "username"
error_message = "incorrect username"
}
}
run "test_run_override" {
# The value in this local override block takes precedence over the
# alternate defined in the file.
override_data {
target = module.credentials.data.aws_s3_object.data_bucket
values = {
body = "{\"username\":\"a_different_username\",\"password\":\"password\"}"
}
}
assert {
condition = jsondecode(local_file.credentials_json.content).username == "a_different_username"
error_message = "incorrect username"
}
}
위 예시에서 파일 레벨에 정의된 재정의 블록은 첫 번째 run 블록에서 사용돼요. 그런 다음 두 번째 run 블록에서는 대체 로컬 재정의가 파일 레벨 재정의보다 우선해요.
마지막으로 override_module 블록으로 전체 모듈을 재정의할 수 있어요. 다음 예시에서 Terraform은 모듈 안의 특정 리소스 대신 모듈 전체를 재정의해요.
# main.tftest.hcl
mock_provider "aws" {}
override_module {
target = module.credentials
outputs = {
data = { username = "username", password = "password" }
}
}
run "test" {
assert {
condition = jsondecode(local_file.credentials_json.content).username == "username"
error_message = "incorrect username"
}
}
이 경우 재정의 블록은 values 대신 outputs를 지정하고, 실제 모듈이 jsondecode 함수를 통해 데이터를 전달하므로 출력 값은 문자열이 아닌 HCL로 지정돼요. override_data 블록과 마찬가지로 override_module 블록도 run 블록에 지정할 수 있으며 같은 우선순위 규칙이 적용돼요.
반복 블록과 중첩 속성 (Repeated blocks and nested attributes)
일부 리소스와 데이터 소스는 반복되는 중첩 속성을 직접 계산된 것으로 지정하며, 리소스의 반복 블록도 계산된 속성을 포함할 수 있어요. 반복 블록과 중첩 속성의 경우 컬렉션의 특정 인스턴스에 대한 값을 지정할 수 없어요. 대신 컬렉션의 모든 인스턴스에 적용되는 단일 값 집합을 제공해야 해요.
예를 들어 aws_dynamodb_table 리소스에는 루트 레벨의 계산된 arn 속성과 replica라는 반복 블록(계산된 arn 속성도 포함)이 있어요. 다음은 aws_dynamodb_table의 간단한 인스턴스 예시예요.
# main.tf
resource "aws_dynamodb_table" "my_table" {
name = "my_table"
hash_key = "key"
attribute {
name = "key"
type = "S"
}
replica {
region_name = "eu-west-2"
}
replica {
region_name = "us-east-1"
}
}
일반적으로 AWS 프로바이더는 DynamoDB 테이블과 지정된 두 replica 모두에 ARN 값을 반환해요. mock_resource 블록이 이 동작을 흉내낼 수는 있지만, 리소스 안의 여러 replica 블록을 구분할 수는 없어요:
mock_resource "aws_dynamodb_table" {
defaults = {
arn = "aws:dynamodb:::my_table"
replica = {
arn = "aws:dynamodb:::my_replica"
}
}
}
이 경우 mock_resource는 DynamoDB 테이블의 ARN에 특정 값을 제공하지만, replica 테이블에 대해 반환되는 ARN 값은 모든 인스턴스 사이에서 공유돼요.