1. 문제의 발단: 혼용된 테스트 문법
Rails 프로젝트에서 Minitest::Spec 스타일을 도입하면 두 가지 설정 문법이 공존하게 되는 경우가 빈번합니다. 개발자마다 선호하는 방식이 다를 수 있기 때문입니다.
- before { ... }: Minitest::Spec에서 제공하는 문법으로, RSpec과 유사한 경험을 제공합니다.
- setup { ... }: Rails의 ActiveSupport에서 제공하는 고유 문법입니다.
저자는 동일한 테스트 파일 내에서 상위 컨텍스트에는 before를, 중첩된(nested) 컨텍스트에는 setup을 사용했을 때 인스턴스 변수가 nil이 되어 NoMethodError가 발생하는 현상을 발견했습니다. 흥미롭게도 이 두 블록의 위치를 바꾸면 테스트가 정상적으로 통과하는 기이한 현상이 나타났습니다. 이는 단순히 문법의 차이가 아니라 실행 순서의 근본적인 차이에서 기인합니다.
2. Minitest의 기본 라이프사이클 이해
일반적인 Minitest의 실행 순서는 다음과 같이 정의됩니다.
1. before_setup: 라이브러리나 프레임워크 확장을 위해 예약된 빈 훅입니다.
2. setup: 실제 테스트 설정을 수행하는 메서드입니다. Minitest::Spec의 before 블록은 내부적으로 이 setup 메서드를 호출하는 문법적 설탕(Syntactic Sugar)에 불과합니다.
3. after_setup: 설정 직후, 테스트 실행 직전에 호출되는 훅입니다.
4. 테스트 케이스 실행: 실제 it 블록이나 test_ 메서드가 실행됩니다.
기본적으로 def setup과 before 블록은 동일한 2단계에서 실행되므로, 순수 Minitest 환경에서는 선언된 순서대로 실행되어 큰 문제가 발생하지 않습니다.
3. Rails의 개입과 실행 순서의 역전
문제는 Rails가 Minitest를 통합하는 방식에 있습니다. Rails는 ActiveSupport::TestCase에 ActiveSupport::Testing::SetupAndTeardown 모듈을 prepend하여 동작 방식을 확장합니다.
여기서 핵심적인 차이가 발생합니다. Rails의 setup 블록은 Minitest의 setup 메서드가 아니라, 그보다 앞선 단계인 before_setup 훅에 등록됩니다. 이로 인해 다음과 같은 실행 계층이 형성됩니다.
- 1단계 (before_setup): Rails의
setup블록들이 파일 내 위치와 상관없이 가장 먼저 실행됩니다. - 2단계 (setup 메서드): Minitest의 표준
def setup또는Minitest::Spec의before블록이 실행됩니다.
결과적으로 코드 상에서 before가 setup보다 위에 작성되어 있더라도, 실제 런타임에서는 Rails의 setup 블록이 무조건 먼저 실행됩니다. 만약 setup 블록이 before 블록에서 초기화할 예정인 인스턴스 변수에 의존하고 있다면, 해당 변수는 아직 nil 상태이므로 오류가 발생하게 되는 것입니다.
4. 해결책 및 권장 사항
이러한 혼란을 방지하고 테스트 스위트의 안정성을 확보하기 위해서는 다음과 같은 실천 방안이 권장됩니다.
- 표준화 (Picking a Standard): 프로젝트 내에서
setup블록을 쓸지,before블록을 쓸지 하나만 정해서 일관되게 사용해야 합니다. 혼용은 잠재적인 버그의 온상이 됩니다. - 역할 분담: 데이터 생성 및 객체 인스턴스화에는
let을 활용하고, 인증 처리나 외부 API 스터빙(stubbing)과 같은 부수 효과가 필요한 설정에만before/setup을 사용하는 식으로 역할을 명확히 구분합니다. - 프레임워크 이해: Rails가 Minitest를 어떻게 래핑하고 있는지 이해하면, 라이프사이클 관련 오류가 발생했을 때 소스 코드를 추적하여 원인을 빠르게 파악할 수 있습니다.