출제기준 세세항목: 단위모듈 구현 · 단위모듈 테스트
설계 단계에서는 시스템을 여러 모듈로 나누고, 각 모듈이 무엇을 받아 무엇을 돌려줄지 정해 두었습니다. 모듈 구현은 이 설계를 실제 코드로 옮기는 단계입니다. 이때 한꺼번에 큰 덩어리를 만들지 않고, 한 가지 기능만 맡는 작은 조각부터 차례로 만들어 갑니다. 이 작은 조각을 단위모듈이라고 부릅니다.
작게 나누어 만드는 이유는 간단합니다. 큰 프로그램을 한 번에 짜고 나서 오류를 찾으려 하면, 문제가 어디서 생겼는지 알기 어렵습니다. 반대로 작은 조각을 하나 만들 때마다 바로 확인해 두면, 나중에 조각을 합쳤을 때 생기는 문제는 '연결 부분'에서 찾으면 됩니다. 그래서 모듈 구현은 '만들기(단위모듈 구현)'와 '바로 확인하기(단위모듈 테스트)'가 한 쌍으로 움직입니다.
단위모듈을 만들 때 가장 먼저 할 일은 내부 코드가 아니라 바깥 모습을 정하는 것입니다. 이 모듈은 어떤 값을 받아서(입력), 무엇을 돌려주는지(출력), 실패하면 어떻게 알리는지를 정합니다. 이 약속이 인터페이스입니다. 인터페이스가 먼저 정해져 있어야 다른 사람이 만드는 모듈과 나중에 맞물릴 수 있습니다.
그다음 그 약속을 지키는 내부 처리 절차, 즉 알고리즘을 코드로 씁니다. 순서로 정리하면 모듈이 할 일을 문서로 정리하는 단위 기능 명세 작성, 입력과 출력 처리를 만드는 인터페이스 구현, 실제 계산이나 판단을 하는 알고리즘(소스 코드) 구현으로 이어집니다. 문서 → 입구·출구 → 속 내용 순서라고 기억하면 됩니다.
구현할 때는 앞 과목에서 배운 원칙이 그대로 적용됩니다. 한 모듈은 한 가지 일만 하게 해서 응집도를 높이고, 다른 모듈의 내부 데이터를 직접 건드리지 않고 필요한 값만 주고받아 결합도를 낮춥니다. 이렇게 만든 모듈은 독립성이 높아서 테스트하기도 쉽고, 다른 곳에 가져가 재사용하기도 쉽습니다. 여러 곳에서 쓰일 기능이라면 공통 모듈로 떼어 내는 것도 이 단계에서 판단합니다.
단위모듈 테스트는 흔히 단위 테스트라고 부르며, 테스트 단계 중 가장 먼저 하는 테스트입니다. 모듈 하나만 따로 떼어 놓고, 명세서에 적힌 대로 동작하는지 확인합니다. 보통 그 모듈을 만든 개발자가 직접 수행합니다.
이 단계에서 주로 찾는 것은 모듈 내부의 잘못입니다. 계산식이 틀렸는지, 조건문의 경계가 잘못되어 한 칸씩 어긋나는지, 예외 상황에서 프로그램이 멈추지는 않는지, 반복문이 끝나지 않는 경우는 없는지 같은 것들입니다. 이런 오류는 여러 모듈을 합친 뒤에 찾으려면 몇 배의 시간이 들기 때문에, 가장 작은 단위에서 먼저 걸러 내는 것이 훨씬 경제적입니다.
단위 테스트 다음에는 모듈을 합쳐 연결 부분을 보는 통합 테스트, 전체 시스템을 보는 시스템 테스트, 사용자가 확인하는 인수 테스트가 이어집니다. 단위 테스트는 이 계단의 첫 칸입니다.
모듈 하나만 떼어 놓으면 곤란한 점이 생깁니다. 원래는 다른 모듈이 이 모듈을 불러 주어야 실행되고, 이 모듈도 일을 하다가 아래쪽 모듈을 불러 결과를 받아야 하는데, 그 상대 모듈이 아직 없거나 테스트에 끌어들이기 곤란할 수 있습니다.
그래서 상대 역할을 대신하는 임시 코드를 만듭니다. 테스트할 모듈을 위에서 불러 주고 결과를 받아 확인하는 쪽을 테스트 드라이버라고 합니다. 반대로 테스트할 모듈이 아래쪽 모듈을 부를 때, 진짜 대신 정해진 값만 돌려주는 가짜 모듈을 테스트 스텁이라고 합니다. 드라이버는 '부르는 쪽 대역', 스텁은 '불리는 쪽 대역'이라고 구분하면 헷갈리지 않습니다.
이 구분은 통합 테스트 방식과도 이어집니다. 아래쪽 모듈부터 합쳐 올라가는 상향식 통합에서는 위쪽 모듈이 아직 없으니 드라이버가 필요하고, 위쪽부터 내려가는 하향식 통합에서는 아래쪽 모듈이 아직 없으니 스텁이 필요합니다.
테스트 방법은 코드 내부를 보느냐 마느냐로 크게 나뉩니다. 화이트박스 테스트는 소스 코드의 구조를 보면서, 모든 문장과 분기와 반복이 실제로 실행되어 보는지를 확인합니다. 단위 테스트에서 특히 많이 쓰입니다. 프로그램 흐름에서 지나갈 수 있는 기본 경로를 빠짐없이 돌려 보는 기초 경로 검사, 조건식의 참·거짓을 골고루 따져 보는 조건 검사, 반복문을 0번·1번·여러 번 돌려 보는 루프 검사, 변수가 정의되고 쓰이는 흐름을 따라가는 데이터 흐름 검사가 여기에 속합니다.
화이트박스 테스트가 얼마나 꼼꼼했는지는 커버리지로 잽니다. 모든 문장을 한 번 이상 실행했는지를 보는 구문 커버리지, 모든 분기의 참과 거짓을 한 번씩 지나갔는지를 보는 결정(분기) 커버리지, 결정 안의 개별 조건까지 참과 거짓을 따지는 조건 커버리지가 대표적이고, 둘을 함께 만족시키는 조건/결정 커버리지도 있습니다.
블랙박스 테스트는 코드 안을 보지 않고, 명세서만 보고 입력을 넣어 기대한 출력이 나오는지 확인합니다. 입력 범위를 성격이 같은 묶음으로 나눠 묶음마다 대표값을 넣는 동등 분할, 오류가 잘 숨어 있는 경계 바로 안팎의 값을 넣는 경계값 분석, 입력 조건과 결과의 관계를 그래프로 정리해 케이스를 뽑는 원인-결과 그래프, 경험상 실수가 잦은 곳을 노리는 오류 예측 등이 있습니다. 화이트박스가 '코드가 짠 대로 도는가'를 본다면, 블랙박스는 '요구한 대로 동작하는가'를 봅니다.
테스트를 감으로 하면 사람마다 결과가 달라집니다. 그래서 무엇을, 어떤 조건에서, 어떤 값으로 넣으면, 어떤 결과가 나와야 하는지를 미리 적어 둡니다. 이것이 테스트 케이스입니다. 보통 식별 번호, 테스트할 항목, 입력값, 실행 조건(사전 상태), 기대 결과를 담습니다. 기대 결과가 미리 적혀 있어야 실제 결과와 비교해 통과와 실패를 판정할 수 있습니다. 이처럼 결과가 맞는지 판단하는 기준을 테스트 오라클이라고 부릅니다.
단위 테스트는 코드를 고칠 때마다 다시 돌려야 의미가 있습니다. 어제 통과한 기능이 오늘 고친 코드 때문에 망가지지 않았는지 확인해야 하기 때문입니다. 이를 손으로 매번 하기는 어려우니 자동화 도구를 씁니다. 자바의 JUnit처럼 언어별로 이어지는 xUnit 계열 도구가 대표적이며, 테스트 코드를 작성해 두면 버튼 한 번으로 전부 실행하고 실패한 케이스만 알려 줍니다.
좋은 단위 테스트는 빠르게 끝나고, 다른 테스트의 결과나 실행 순서에 영향을 받지 않으며, 몇 번을 돌려도 같은 결과가 나옵니다. 실패했을 때 어느 부분이 왜 틀렸는지 바로 알 수 있도록 테스트 하나가 한 가지만 확인하게 만드는 것도 중요합니다.
'3만 원 이상 구매 시 무료 배송' 모듈을 단위 테스트하면서 29,999원, 30,000원, 30,001원을 넣어 보았습니다. 30,000원에서 배송비가 붙는 오류가 나왔는데, 조건을 '이상(>=)'이 아니라 '초과(>)'로 쓴 탓이었습니다. 경계값 분석이 노리는 전형적인 실수입니다.
주문 모듈은 내부에서 결제 모듈을 부르는데, 테스트 때마다 실제 결제를 일으킬 수는 없습니다. 그래서 '결제 성공' 또는 '잔액 부족'을 정해진 대로 돌려주는 스텁을 만들고, 주문 모듈을 호출해 결과를 확인하는 드라이버를 붙여 주문 모듈만 따로 검증했습니다.