TL;DR

  • 이미지 형식별 테스트 사례를 이름 있는 필드로 선언하고 각 사례를 독립된 테스트 함수로 생성하는 Rust 매크로 구현임.
  • 반복문 방식은 사례 하나가 실패하면 나머지 검증을 건너뛰며, 위치 인수 매크로는 매개변수가 많을 때 가독성이 낮아짐.
  • parameterized, rstest, yare 같은 라이브러리 대신 학습을 위해 선언형 매크로를 직접 작성함.
  • 매크로의 매처(matcher)는 테스트 사례 입력 패턴과 메타변수를 정의하고, 전사기(transcriber)는 캡처한 값으로 #[test] 함수들을 생성함.
  • 각 사례가 별도 테스트로 실행되므로 코드 중복 없이 개별 테스트 함수의 이점을 유지함.

기존 접근 방식

  • Rust에서 매개변수화 테스트를 작성하는 방법으로 테스트 사례 배열을 반복하는 방식, 위치 인수 매크로, 서드파티 테스트 라이브러리를 검토함.
  • 반복문 방식은 사례를 배열로 선언한 뒤 하나씩 검사하는 가장 단순한 방법임.
  • Rust의 표준 어설션은 사례별 격리를 제공하지 않으므로 한 사례가 실패하면 테스트가 즉시 중단되고 나머지 사례는 실행되지 않음.
  • Go 테스트 라이브러리의 t.Error는 테스트 실패를 기록하면서도 실행을 이어가므로, 즉시 중단하는 방식보다 더 많은 정보를 얻을 수 있음.
  • 위치 인수 매크로는 테스트 이름을 붙일 수 있지만, 매개변수가 몇 개만 늘어도 각 값의 의미를 파악하기 어려워짐.
  • parameterized, rstest, yare 같은 크레이트는 속성(attribute)으로 테스트 사례를 선언하는 방식임.
  • 대규모 코드베이스라면 이러한 라이브러리를 비교해 선택할 수 있지만, 개인용 소규모 도구에서는 직접 기본 기능을 만들어 Rust를 학습하는 쪽을 선택함.

직접 매크로 작성

  • 이미지 썸네일 생성 테스트에서 애니메이션 GIF, 정적 GIF, JPEG 사례를 각각 이름 붙이고, 이미지 경로·대상 크기·기대 파일명·기대 크기를 지정함.
  • 사례 선언만 읽어도 테스트 내용과 새 사례를 추가하는 방법을 알 수 있도록 이름 있는 필드로 구성함.
  • 매크로는 각 사례마다 #[test]가 붙은 함수를 생성함.
  • 함수는 이미지 경로로 PathBuf를 만들고 테스트 출력 디렉터리를 가져온 뒤, 지정된 대상 크기로 썸네일을 생성함.
  • 생성된 경로가 기대 파일명과 일치하는지, 파일이 실제로 존재하는지, 이미지 크기가 기대값과 같은지 확인함.

선언형 매크로의 구조

  • 선언형 매크로는 입력 패턴을 정의하는 왼쪽 부분인 매처와, 캡처한 값으로 Rust 코드를 만드는 오른쪽 부분인 전사기로 구성됨.
  • 매처는 입력 코드가 일치해야 하는 Rust 토큰 패턴을 지정하며 쉼표와 괄호도 정확히 맞아야 하지만 공백은 무시됨.
  • $(…)*는 괄호 안 패턴이 0회 이상 반복되는 구조임.
  • $name:ident는 Rust 식별자를, $img_path:expr는 Rust 표현식을 매칭해 메타변수에 저장함.
  • 매크로 입력이 반드시 유효한 Rust 코드일 필요는 없지만, 익숙한 구조체 문법과 비슷한 형태로 설계함.
  • 전사기는 컴파일 시점에 메타변수를 대입해 생성할 Rust 코드의 템플릿임.
  • $(…)*가 입력 사례마다 반복되며 해당 이름과 값으로 #[test] 함수 하나를 생성함.
  • Rust에서는 매크로 없이 테스트 함수를 동적으로 생성할 방법이 없으므로 이 구조에 매크로가 필요함.

컴파일 결과와 테스트 실행

  • 컴파일 과정에서 매크로 호출은 각 테스트 함수를 직접 작성한 것과 같은 코드로 대체됨.
  • 모든 테스트 사례가 별도 테스트로 실행되므로 많은 상용구 코드를 복사하지 않고도 개별 테스트 함수의 이점을 얻음.
  • 이전에는 프로젝트마다 같은 코드 조각을 생각 없이 복사했지만, 이제 매크로의 괄호와 별표가 하는 일을 이해해 이후 매크로 작성과 읽기에 도움이 됨.