Abseil Tip 135 계약을 테스트하라, 구현을 테스트하지 마라

주간 팁 #135: 계약을 테스트하라, 구현을 테스트하지 마라

Originally posted as TotW #135 on June 5, 2017
By James Dennett
Updated 2020-04-06

“진정한 친구가 한 명이라도 있다면, 당신은 이미 많은 것을 가진 것이다.” — 토마스 풀러(Thomas Fuller)

C++에는 public, protected, private, 그리고 friend를 사용하는 꽤 복잡한 접근 제어 메커니즘이 있습니다. 테스트 코드에는 이러한 기능을 사용하는 자체 규칙이 있으며, GoogleTest는 FRIEND_TEST 매크로를 통해 이를 보완합니다. 하지만 FRIEND_TEST는 최후의 수단으로 사용해야 하며, 선호되는 선택은 아닙니다.

계약을 테스트하라

우리가 테스트를 작성하는 이유는 컴포넌트의 계약(Contract)에서 발생할 수 있는 버그를 찾거나, 그런 버그가 없다는 충분한 확신을 얻기 위해서입니다. 테스트 주도 개발(TDD)을 사용할 때는, 그 계약을 설계하는 데 도움을 주기 위해 테스트를 작성합니다. 컴포넌트의 명시되지 않은 측면에 의존하는 테스트는 취약하며, 실제로는 프로덕션 코드가 제대로 작동하고 있음에도 불구하고 실패를 보고할 가능성이 높습니다.

컴포넌트의 공개 인터페이스(public interface)를 통해 테스트하는 것을 우선시하십시오. 더 일반적으로, 테스트는 컴포넌트의 계약을 검증해야 하며, 다른 클라이언트와 마찬가지로 보장된 사항 외에는 가정하지 않아야 합니다.

테스트에서 접근 권한을 제공하는 기법

테스트 코드가 필요한 접근 권한을 얻을 수 있도록 하기 위해 여러 가지 기법이 있습니다. 아래에 나열된 방법들은 대체로 좋은 것에서 나쁜 순서로 정리되어 있습니다.

테스트를 위한 공용 API 확장

최소한의 인터페이스를 통해 테스트하는 것이 충분한 커버리지를 제공하기 어려운 경우가 있습니다. 만약 컴포넌트가 매우 좁은 인터페이스(예: ProcessItem 가상 함수만 있는 베이스 클래스)를 구현하고 있고, 그 인터페이스만으로는 충분한 신뢰를 얻기 어려운 경우, 구현 세부 사항을 포함하는 새로운 테스트 가능한 컴포넌트를 생성하는 것을 고려하십시오. 이렇게 하면 가상 함수를 포함한 클래스는 매우 단순해져서 최소한의 테스트만 필요하게 됩니다. 빌드 시스템이 지원하는 경우, BUILD 가시성을 사용해 구현 클래스를 제한적으로 사용할 수도 있습니다.

테스트가 단지 한두 개의 비공개 함수에만 의존하는 경우, 해당 함수를 공용 인터페이스의 일부로 만드는 것을 고려하십시오. 이것은 그리 나쁜 선택은 아닙니다. 어쨌든 명확하게 문서화된 인터페이스를 필요로 하며, 다른 클라이언트(테스트뿐만 아니라)에게도 유용할 수 있습니다. 만약 이 함수가 정말로 테스트만을 위한 것이라면, 이를 명시적으로 문서화하고 ForTesting 접미사를 붙이는 것이 좋습니다.

구현 노출을 피하기 위한 테스트 피어 사용

여전히 테스트에서 비공개 구현 세부 사항에 접근해야 하는 경우, 테스트 피어(test peer) (때로는 테스트 배우자(test spouse)라고도 함)를 만드십시오. 테스트 피어는 테스트 대상 클래스의 friend 클래스이며, 보통 _test.cc 파일에 정의됩니다 (때로는 테스트 대상 클래스를 befriends하는 파일에 정의하기도 합니다). 테스트 피어는 테스트 코드에 대한 제어된 접근을 제공합니다. 테스트 피어는 익명 네임스페이스에서 사용할 수 없지만, 나머지 테스트 코드는 여전히 익명 네임스페이스에서 사용할 수 있습니다. 테스트 피어 클래스의 이름은 일반적으로 Peer로 끝납니다.

FRIEND_TEST 사용 지양

과거의 코드에서는 흔히 사용되었지만, FRIEND_TEST새로운 코드에서 사용하지 말아야 합니다. 이는 역방향 결합을 도입하여, 프로덕션 헤더 파일이 관련된 단위 테스트의 세부 사항에 의존하게 만듭니다. 테스트를 익명 네임스페이스 밖으로 이동시키며, 각 테스트 함수에 대해 무제한 접근을 허용하기 때문에, 긴 테스트 함수에서는 테스트가 클래스 상태를 변경하는 부분을 파악하기 어렵습니다. 또한, GoogleTest가 제공하는 비정상적인 헤더 파일을 프로덕션 코드에 포함하도록 강요합니다. 마지막으로, 확장성도 나쁘며, 새로운 테스트를 추가할 때마다 프로덕션 헤더 파일에 새로운 FRIEND_TEST를 추가해야 합니다. 이는 실제로 헤더 파일이 긴 FRIEND_TEST 줄로 채워지게 만듭니다.

전체 테스트 픽스처를 friend로 만들지 말 것

테스트 대상 클래스의 전체 테스트 픽스처를 friend로 만드는 것(friend class MyClassTest;)은 강력히 권장되지 않습니다. 위에서 언급한 방법들에 비해, 전체 테스트 픽스처(테스트 자체가 아닌 픽스처의 서브클래스)에게 테스트 대상 클래스의 모든 멤버에 대한 무제한 접근을 허용합니다. 이로 인해 테스트 코드 독자는 테스트가 캡슐화를 깨는 부분을 시각적으로 파악할 수 없습니다. 또한 테스트 픽스처를 익명 네임스페이스 밖으로 이동시킵니다. friend 픽스처를 사용하는 것보다 테스트 피어를 사용하는 것이 코드 독자에게 더 자가 문서화(self-documenting)되며, 코드 작성자에게는 약간의 추가 작업만 필요합니다.

권장 사항 요약

  • 컴포넌트의 클라이언트 인터페이스를 테스트하고, 테스트를 비공개 구현 세부 사항과 독립적으로 유지하십시오.
  • 클라이언트 인터페이스만으로 충분한 테스트가 어려울 경우, 테스트 가능한 (테스트 전용일 수도 있는) 하위 컴포넌트로 분리하십시오.
  • 컴포넌트를 테스트 가능하게 하기 위해 공용 인터페이스를 추가하는 것도 때로는 합리적입니다.
  • 필요하다면, FRIEND_TEST 대신 테스트 피어를 사용하여 비공개 멤버에 접근하십시오.
  • 전체 테스트 픽스처를 friend로 만들지 마십시오. 위에서 설명한 더 구체적인 접근 방법을 사용하십시오.