LabHub
배우기 러닝패스 코스

Terraform 실전 · 모듈에 시험을 붙인다 · 이론

인프라 코드에도 시험을 붙인다

LabHub 에서 이어서 보기

한 줄 요약

.tftest.hcl값싼 시험(계획만)비싼 시험(실제로 만들기)을 한 문법으로 쓰게 해 준다. 둘을 섞어 쓰는 감각이 전부다.

왜 이게 필요했나 — 고칠 수 없는 모듈이 되는 과정

모듈을 처음 만들 때는 부르는 곳이 하나다. 고치고 돌려 보면 끝난다. 반년 뒤에는 열두 곳이 부른다. 이제 한 줄을 고칠 때마다 열두 곳이 어떻게 되는지 확인해야 하는데, 그럴 시간이 없으니 사람들은 다른 길을 고른다 — 고치지 않는다. 새 요구가 오면 모듈을 고치는 대신 옆에 비슷한 모듈을 하나 더 만든다. 그렇게 모듈이 늘어나고, 어느 것이 최신인지 아무도 모르게 된다.

시험은 이 악순환을 끊는 장치다. "이 값을 바꾸면 어디가 깨지나" 를 명령 한 줄로 바꿔 주기 때문이다. 그런데 인프라 코드의 시험에는 고민이 하나 더 있다. 정말 만들어 봐야 아는 것값만 보면 아는 것이 섞여 있다는 점이다.

어떻게 동작하나

시험 파일은 .tftest.hcl 확장자를 쓰고, 현재 디렉터리나 tests 디렉터리에서 찾는다. 파일 안에는 run 블록이 순서대로 들어간다.

variables {          # 이 파일의 모든 run 이 쓰는 기본 입력  env = "dev"}run "name_is_prefixed" {  command = plan     # 아무것도 만들지 않는다  assert {    condition     = output.name == "app-dev"    error_message = "이름 규칙이 깨졌습니다"  }}

command = plancommand = apply 의 차이가 핵심이다. 계획만 세우는 시험은 빠르고 아무것도 만들지 않아서, 이름 규칙·조건 분기·기본값·계산된 값처럼 값만 보면 되는 것을 전부 덮을 수 있다. 실제로 만드는 시험은 느리고 자원을 쓰지만, "정말 그 파일이 그 내용으로 생기는가" 는 이 방법으로만 알 수 있다. 끝나면 도구가 자기가 만든 것을 지운다.

run 은 이어 붙일 수 있다. 뒤 run 에서 앞 run 의 출력을 이름으로 참조한다. "먼저 네트워크를 만들고, 그 아이디로 앱을 계획한다" 같은 단계가 이렇게 표현된다.

expect_failures 는 뒤집어 말하는 장치다. 이 run 은 실패해야 통과라고 선언한다. validation 이 정말 막는지, 잘못된 입력이 정말 거절되는지를 시험할 때 쓴다. 막아 주는 장치를 만들어 놓고 확인하지 않으면, 어느 날 조건을 잘못 고쳐도 아무도 모른다.

mock_provider 는 프로바이더를 흉내 낸다. 실제로 만들지 않고 그럴듯한 값을 돌려주므로, 느리거나 돈이 드는 프로바이더를 시험할 때 값어치가 크다. 대신 흉내이므로 "진짜 그렇게 생기는가" 는 확인하지 못한다. 진짜 apply 시험과 역할이 다르다는 것을 분명히 해 두어야 한다.

실패했을 때의 메시지도 봐 둘 값어치가 있다. 단언이 있던 줄과 그때 값이 실제로 무엇이었는지를 함께 보여 준다. 그래서 error_message 에 값을 되풀이해 적을 필요가 없다.

현장에서 만나는 모습

가장 흔한 사고는 CI 스크립트가 종료 코드를 삼키는 것이다. 시험은 실패했는데 스크립트가 0 으로 끝나면 파이프라인은 초록불로 지나간다. 이건 시험이 없는 것보다 나쁘다 — 아무도 확인하지 않으면서 확인했다고 믿게 만들기 때문이다. 그래서 스크립트를 만들었으면 일부러 실패하는 시험을 하나 끼워 넣어 빨간불이 켜지는지 한 번 봐야 한다.

두 번째는 비싼 시험만 쓰는 것이다. 전부 apply 로 쓰면 시험 한 바퀴가 길어지고, 길어지면 사람들이 돌리지 않는다. 대부분을 plan 으로 옮기고, 실제 생성은 꼭 필요한 몇 개만 남기는 것이 실무의 균형이다.

세 번째는 시험이 남기는 찌꺼기다. 도구가 만든 것을 지우기는 하지만, 시험이 중간에 죽으면 남을 수 있다. 공용 계정에서 시험을 돌린다면 이름에 구분자를 넣어 두는 편이 안전하다.

그리고 잊기 쉬운 사실 하나 — 시험 파일도 코드다. 리뷰를 받고, 모듈과 같은 저장소에 살고, 모듈을 고칠 때 함께 고쳐진다. 시험이 모듈 밖 어딘가에 따로 살면 반년 뒤에는 아무도 돌리지 않는 상태가 된다.

네 번째는 무엇을 시험하지 않을지 정하지 않는 것이다. 프로바이더가 이미 보장하는 것(값을 넣으면 그 값이 들어간다)을 되풀이해 시험하면 시험 수만 늘고 얻는 것이 없다. 인프라 코드의 시험이 지켜야 할 것은 우리가 만든 규칙이다 — 이름 규칙, 환경별 분기, 허용하지 않는 입력, 층 사이의 연결. 프로바이더의 동작이 아니라 우리의 결정이 시험 대상이다.

마지막으로 시험은 모듈을 고칠 수 있게 만드는 장치라는 점을 기억하면 우선순위가 정해진다. 가장 먼저 시험을 붙일 곳은 가장 많이 불리는 모듈, 그중에서도 최근에 사고가 났던 지점이다. 완벽한 덮개를 목표로 삼으면 시작을 못 하지만, 사고가 났던 자리 하나에 시험을 붙이는 일은 오늘 할 수 있다.

다음 실습에서 할 것

/root/tfa-test 에 변수 두 개와 출력 세 개를 가진 모듈을 만들고, 계획만 세우는 시험과 실제로 만드는 시험을 각각 붙입니다. run 을 이어 붙여 앞 결과를 다음 입력으로 쓰고, expect_failures 로 validation 이 막는 것을 확인하고, 일부러 실패하는 시험을 돌려 메시지와 종료 코드를 보고, mock_provider 로 값만 확인한 뒤, CI 가 부를 스크립트로 묶어 양쪽 방향(통과·실패)이 모두 옳게 끝나는지 확인합니다.