[IT-방법] Rust Cargo 기초 테스트 작성법 – 안정적인 코드를 만드는 5단계 전략

Rust Cargo 기초 테스트를 설명하는 대표 이미지

Rust Cargo 기초 테스트가 왜 개발의 시작일까요?

열심히 코드를 작성하고 `cargo build` 명령어를 입력했을 때, 컴파일 오류 없이 실행까지 잘 된다고 안심하신 적이 있나요? 하지만 실행은 되는데 결과값이 예상과 다르거나, 특정 조건에서 프로그램이 갑자기 종료되는 상황을 마주하면 당혹스러울 수밖에 없어요. 특히 프로젝트 규모가 커지면서 기존에 잘 작동하던 기능이 새로운 코드를 추가할 때마다 망가지는 현상을 경험했다면, 그것은 바로 테스트 코드의 부재 때문이에요.

Rust는 메모리 안전성을 강력하게 보장하는 언어이지만, 비즈니스 로직의 오류까지 막아주지는 않아요. 개발자가 작성한 수학적 계산이 틀렸거나, 조건문이 잘못 설계되었다면 프로그램은 논리적 오류를 뿜어내며 작동해요. 이때 Rust Cargo 기초 테스트 시스템을 제대로 활용한다면, 코드를 수정할 때마다 수동으로 확인하던 번거로운 과정을 획기적으로 줄일 수 있어요.

단순히 오류를 찾는 것을 넘어, 테스트는 여러분이 작성한 코드가 의도대로 동작한다는 것을 증명하는 가장 강력한 도구예요. 잘 짜인 테스트 코드는 나중에 코드를 리팩토링할 때 든든한 버팀목이 되어주고, 동료 개발자들에게는 이 코드가 어떻게 사용되어야 하는지를 보여주는 살아있는 설명서 역할을 수행해요.

이 글을 끝까지 읽고 나면 다음과 같은 능력을 갖추게 될 거예요.

  • 어떤 코드를 테스트해야 효율적인지 판단하는 기준을 세울 수 있어요.
  • Cargo 명령어를 활용해 유닛 테스트와 통합 테스트를 자유롭게 실행할 수 있어요.
  • 문서 테스트를 통해 코드와 설명을 동시에 관리하는 법을 익혀요.
  • CI 도구를 활용해 매번 자동으로 테스트가 돌아가는 환경을 구축해요.

테스트 작성을 위한 기본 환경과 개념 정리

본격적으로 테스트 코드를 작성하기 전에, 우리가 어떤 무기를 사용할 수 있는지 먼저 파악해야 해요. Rust는 다른 언어와 달리 별도의 테스트 프레임워크를 설치하지 않아도 Cargo 내부에 강력한 테스트 도구가 내장되어 있어요. 따라서 환경 설정에 대한 부담 없이 바로 시작할 수 있다는 점이 큰 장점이에요.

하지만 테스트에도 종류가 있다는 사실을 잊으면 안 돼요. 모든 코드를 똑같은 방식으로 테스트하려고 하면 시간만 낭비하고 정작 중요한 오류는 놓칠 수 있거든요. 테스트의 목적과 범위에 따라 크게 세 가지 유형으로 나누어 관리하는 것이 효율적이에요.

테스트 유형 주요 목적 작성 위치 장점
유닛 테스트 개별 함수/구조체 검증 소스 파일 내부 빠른 실행, 세밀한 검증
통합 테스트 모듈 간 상호작용 검증 tests/ 폴더 실제 사용자 관점 검증
문서 테스트 예제 코드의 정확성 검증 코드 주석 내부 문서와 코드 동기화

테스트 대상을 선정할 때는 로직이 복잡한 곳부터 시작하는 것이 좋아요. 단순히 값을 반환하는 함수보다는 조건문이 많거나 수학적 계산이 들어가는 함수가 테스트의 가치가 훨씬 높기 때문이에요. 반대로 Getter/Setter처럼 단순한 함수에 너무 많은 시간을 쏟는 것은 지양해야 해요.

💡 알아두기
Rust에서 테스트를 실행할 때 사용하는 기본 명령어는 `cargo test`예요. 이 명령어 하나로 유닛 테스트, 통합 테스트, 문서 테스트를 모두 한 번에 수행할 수 있어요.

또한, 테스트를 작성할 때는 격리성을 유지해야 해요. 테스트 A가 성공하기 위해 테스트 B가 먼저 실행되어야 하거나, 특정 파일이 반드시 존재해야 한다면 그 테스트는 나쁜 테스트예요. 각 테스트는 마치 독립적인 세계에서 실행되는 것처럼 설계되어야 어떤 환경에서도 안정적으로 통과할 수 있어요.

안정적인 코드를 위한 단계별 테스트 실행 전략

이제 준비는 끝났어요. 실제로 코드를 어떻게 작성하고 실행하는지 구체적인 단계를 통해 살펴볼게요. 단순히 문법을 배우는 것이 아니라, 실무에서 어떤 흐름으로 테스트를 구축하는지에 집중해 주세요.

STEP 1. 유닛 테스트로 코드의 최소 단위 검증하기

유닛 테스트는 가장 작고 세밀한 단위인 함수나 메서드를 검증하는 단계예요. Rust에서는 별도의 파일을 만들지 않고, 소스 코드 파일 하단에 `mod tests` 블록을 만들어 작성하는 것이 관례예요. 이렇게 하면 테스트 코드가 실제 소스 코드와 가까이 있어 관리하기 편리해요.

테스트 함수 위에는 반드시 #[test] 속성을 붙여줘야 Cargo가 이 함수를 테스트로 인식해요. 가장 많이 사용하는 검증 도구는 assert_eq!(a, b)예요. 이는 a와 b가 같은지 확인하고, 다를 경우 테스트를 실패 처리해요. 만약 두 값이 달라야 한다면 assert_ne!(a, b)를 사용하면 돼요.

예를 들어, 두 숫자를 더하는 함수가 있다면 다음과 같은 방식으로 테스트를 구성할 수 있어요. 단순히 결과값만 확인하는 게 아니라, 음수가 들어왔을 때나 0이 들어왔을 때 같은 경계 조건(Edge Case)도 반드시 포함해야 해요. 이것이 초보 개발자와 숙련된 개발자를 가르는 차이점이에요.

STEP 2. 통합 테스트로 시스템의 흐름 파악하기

유닛 테스트가 개별 부품이 잘 작동하는지 보는 것이라면, 통합 테스트는 부품들을 조립했을 때 기계가 제대로 돌아가는지 확인하는 과정이에요. 이 테스트들은 프로젝트 루트 디렉토리에 tests/라는 폴더를 만들고 그 안에 별도의 `.rs` 파일을 생성하여 작성해요.

통합 테스트의 핵심은 외부 사용자 관점에서 코드를 바라보는 것이에요. 즉, 해당 모듈의 내부 구현 방식(private 함수)은 무시하고, 오직 공개된 인터페이스(public 함수/구조체)만을 사용하여 전체적인 흐름을 검증해야 해요. 이렇게 하면 내부 구현을 리팩토링하더라도, 외부 인터페이스가 유지되는 한 테스트는 깨지지 않아 유지보수가 매우 쉬워져요.

⚠️ 주의
통합 테스트 작성 시 내부의 private 함수를 테스트하려고 시도하지 마세요. 이는 설계 결함일 가능성이 높으며, 테스트의 목적(사용자 관점 검증)에도 어긋나요.

STEP 3. 문서 테스트로 살아있는 예제 만들기

Rust의 가장 매력적인 기능 중 하나는 바로 문서 테스트(Doc Test)예요. 함수의 기능을 설명하는 주석(doc comment, `///`) 안에 코드 예제를 넣어두면, Cargo가 이를 추출해 실제로 실행해 봐요. 이는 문서의 정확성을 보장할 뿐만 아니라, 다른 개발자가 여러분의 코드를 어떻게 사용해야 하는지 알려주는 훌륭한 가이드가 돼요.

문서 테스트를 작성할 때는 단순히 성공하는 코드만 넣지 마세요. 사용자가 흔히 할 수 있는 실수나 주의해야 할 사항을 코드로 보여주는 것도 좋은 방법이에요. 이렇게 작성된 예제는 코드를 수정할 때 문서와 함께 자동으로 검증되므로, 시간이 흘러 문서가 실제 코드와 달라지는 현상을 완벽하게 방지할 수 있어요.

STEP 4. CI 환경을 통한 테스트 자동화 구축하기

테스트 코드를 아무리 잘 짜도, 개발자가 매번 수동으로 실행하는 것을 잊어버리면 아무런 의미가 없어요. 그래서 우리는 CI(지속적 통합) 환경을 구축해야 해요. 가장 대중적인 방법은 GitHub Actions를 사용하는 것이에요. 프로젝트 루트에 `.github/workflows/` 폴더를 만들고 설정 파일을 추가하기만 하면 돼요.

설정 파일에는 다음과 같은 흐름이 포함되어야 해요. 1) 코드가 푸시되거나 Pull Request가 생성될 때, 2) 가상 환경(Ubuntu 등)을 실행하고, 3) Rust 도구 모음을 설치한 뒤, 4) `cargo test`를 자동으로 실행하도록 구성하는 것이죠. 만약 테스트 중 단 하나라도 실패한다면, CI는 빨간 불을 켜고 머지(Merge)를 막아줍니다. 이것이 바로 안정적인 소프트웨어를 만드는 자동화된 방어선이에요.

STEP 5. 테스트 실행 최적화 및 디버깅 전략

프로젝트 규모가 커지면 테스트 실행 시간이 길어져 답답함을 느낄 수 있어요. 이럴 때는 전체 테스트 대신 특정 테스트만 골라서 실행하는 방법을 익혀야 해요. `cargo test test_name`처럼 특정 이름을 지정하거나, `cargo test — –nocapture` 옵션을 사용하여 테스트 중 출력되는 `println!` 메시지를 실시간으로 확인할 수 있어요.

테스트가 실패했을 때 당황하지 마세요. Rust의 에러 메시지는 매우 친절하게 설계되어 있어요. 어느 파일의 몇 번째 줄에서 어떤 값 때문에 실패했는지 정확히 짚어주기 때문에, 메시지를 꼼꼼히 읽는 것만으로도 문제의 80% 이상은 파악할 수 있어요. 만약 로직이 너무 복잡해 원인을 찾기 힘들다면, 테스트 코드 안에 임시로 로그를 남기거나 디버거를 연결해 상태를 추적하는 습관을 들이는 것이 좋아요.

💡 알아두기
테스트 속도를 높이기 위해 불필요하게 무거운 데이터베이스 연결이나 네트워크 통신을 테스트에 포함하는 것은 피해야 해요. 이런 경우에는 Mock(가짜 객체)을 활용하여 외부 의존성을 분리하는 것이 권장됩니다.

자주 하는 실수와 해결법 및 궁금한 점

자주 하는 실수와 해결법

  • 모든 코드에 테스트를 작성하려고 함
    왜 발생하는가: 완벽주의 때문에 사소한 Getter 함수까지 테스트를 만드느라 시간을 낭비해요.
    ✅ 해결법: 로직이 포함된 함수와 경계 조건이 존재하는 구간을 우선순위로 두세요.
  • 테스트 코드에서 private 함수를 직접 호출함
    왜 발생하는가: 유닛 테스트를 작성할 때 내부 로직을 검증하고 싶은 욕심 때문이에요.
    ✅ 해결법: private 함수는 public 함수를 통해 간접적으로 검증하거나, 꼭 필요하다면 해당 모듈 내에서 유닛 테스트로 처리하세요.
  • 실패할 수 있는 상황을 고려하지 않음
    왜 발생하는가: ‘해피 패스(Happy Path)’, 즉 모든 게 잘 작동하는 상황만 가정하기 때문이에요.
    ✅ 해결법: 잘못된 입력값, 오버플로우, 빈 데이터 등 예외적인 상황을 반드시 테스트 케이스에 포함하세요.
  • 테스트 환경이 오염됨
    왜 발생하는가: 테스트 실행 시 생성된 임시 파일이나 DB 데이터가 삭제되지 않고 남아 다음 테스트에 영향을 줘요.
    ✅ 해결법: 테스트 시작 전후에 환경을 초기화하거나, 고유한 임시 경로를 사용하도록 설계하세요.
  • CI 연동을 미룸
    왜 발생하는가: 설정이 번거롭고 당장 코드를 짜는 게 급하다고 생각하기 때문이에요.
    ✅ 해결법: 프로젝트 초기 단계부터 CI를 설정하세요. 나중에 규모가 커진 뒤에 구축하려면 훨씬 많은 비용이 들어요.

자주 묻는 질문

Q. `cargo test` 실행 시 결과가 너무 많아서 보기 힘들어요. 어떻게 하면 좋나요?

특정 테스트 그룹만 보고 싶다면 `cargo test [테스트이름]` 형식을 사용해 보세요. 혹은 `cargo test — –list` 명령어를 사용하면 현재 프로젝트에서 실행 가능한 모든 테스트 목록을 깔끔하게 확인할 수 있어요.

Q. 테스트 중에 `println!`으로 찍은 내용이 안 보여요. 이유가 뭔가요?

Cargo는 테스트가 성공하면 출력을 생략하는 것이 기본 설정이에요. 만약 결과를 직접 확인하고 싶다면 `cargo test — –nocapture` 옵션을 추가해서 실행해 보세요. 그러면 성공한 테스트의 표준 출력도 모두 볼 수 있어요.

Q. 테스트를 작성하는 게 코드 작성 속도를 늦추는 것 같아 걱정돼요.

처음에는 분명 느리게 느껴질 수 있어요. 하지만 테스트 없이 작성한 코드는 나중에 버그를 수정하는 데 수십 배의 시간을 쓰게 만들어요. 테스트는 속도를 늦추는 장애물이 아니라, 나중에 발생할 거대한 장애물을 미리 치워주는 투자라고 생각하시면 좋아요.

Q. 문서 테스트(Doc Test)는 언제 사용하는 게 가장 좋나요?
공개 라이브러리나 API를 설계할 때 가장 유용해요. 사용자가 내 라이브러리를 어떻게 쓰는지 보여주는 예제 코드가 틀리지 않았다는 것을 보장해 주기 때문에, 문서의 신뢰도를 높이는 데 최고의 방법이에요.

안정적인 Rust 개발을 위한 요약과 다음 단계

지금까지 Rust Cargo를 활용한 기초 테스트 전략을 살펴보았어요. 테스트는 단순히 버그를 잡는 도구가 아니라, 여러분의 코드가 가진 가치를 증명하는 과정이라는 점을 꼭 기억해 주세요. 처음부터 완벽할 필요는 없어요. 작은 함수 하나부터 테스트를 작성하는 습관을 들여보세요.

✅ 핵심 요약

  • 유닛 테스트는 함수 단위로, 통합 테스트는 모듈 간 상호작용 중심으로 작성하세요.
  • 경계 조건(Edge Case) 테스트를 통해 예외 상황을 반드시 검증하세요.
  • 문서 테스트를 활용해 예제 코드와 실제 코드의 일치성을 유지하세요.
  • CI 도구를 활용해 모든 코드가 푸시될 때마다 자동으로 테스트를 실행하세요.
  • 테스트는 내부 구현이 아닌 공개된 인터페이스를 기준으로 작성해야 합니다.

이제 이론을 넘어서 실전으로 나아갈 시간이에요. 오늘 바로 실천할 수 있는 단계를 제안해 드릴게요.

  • 오늘 할 일: 현재 작성 중인 코드 중 로직이 가장 복잡한 함수 하나를 골라 유닛 테스트를 작성해 보세요.
  • 이번 주 할 일: GitHub Actions를 연동하여 코드를 푸시할 때마다 테스트가 돌아가는 환경을 만드세요.
  • 실행 직전 할 일: 테스트 대상 함수에 대해 ‘어떤 잘못된 입력이 들어올 수 있을까?’를 3가지 이상 리상해 보세요.

Rust Cargo 기초 테스트를 믿을 수 있게 만드는 견고한 테스트를 작성해 보세요! 여러분의 코드는 훨씬 더 강력하고 신뢰할 수 있는 자산이 될 거예요.

함께 읽으면 좋은 글: Rust Cargo 기초 관련 다른 글입문 Rust 학습 가이드

댓글 남기기