꿈돌이랜드

단위 테스트 - 단위 테스트 안티 패턴 본문

Programming/OS

단위 테스트 - 단위 테스트 안티 패턴

loinsir 2023. 11. 14. 10:19
반응형

이 글은 단위 테스트 도서를 읽고 정리한 글입니다.

비공개 메서드(private method) 단위 테스트

Q. 비공개 메서드를 어떻게 테스트하는가?

A. 전혀 하지 말아야 한다.

  • 단위 테스트를 하려고 비공개 메서드를 노출하는 경우는 식별할 수 있는 동작만 테스트하는 것을 위반한다.
  • 비공개 메서드를 노출하는 것은 테스트가 구현 세부 사항과 결합되고,
  • 결과적으로 리팩터링 내성이 떨어진다.
  • 비공개 메서드를 직접 테스트하는 대신, 포괄적인 식별할 수 있는 동작으로서 간접적으로 테스트하는 것이 좋다.

때로는 비공개 메서드가 너무 복잡해서 식별할 수 있는 동작으로 테스트하기에 충분히 커버리지를 얻지 못하는 경우에는 다음에 대해 고민해본다.

  • 식별할 수 있는 동작에 이미 합리적인 테스트 커버리지가 있다고 가정해보면, 다음 두 가지 문제가 발생할 수 있다.
    1. 이미 해당 코드는 죽은 코드이다.
      • 해당 코드는 어디에도 사용되지 않고, 리팩터링 후에도 남아서 관계 없는 코드 일 수 있다. 이러한 코드는 삭제하자
    1. 추상화가 누락된 경우이다.
      • 비공개 메서드가 너무 복잡하면 별도의 클래스로 도출해야 하는 추상화가 누락됐다는 징후이다.

비공개 메서드 테스트가 타당한 경우

위와 같은 비공개 메서드 테스트의 규칙에도 예외가 있다.

  • 비공개 메서드를 테스트하는 것 자체는 나쁘지 않다. 다만, 비공개 메서드가 구현 세부 사항의 프록시에 해당하므로 나쁜 것이다.
  • 구현 세부 사항을 테스트하면 궁극적으로 테스트가 깨지기 쉽다.
  • 그렇기는 해도 메서드가 비공개이면서 식별할 수 있는 동작인 경우는 드물다.
    • 이런 드문 경우의 예시는, private initializer가 있다. (식별할 수 있으면서 비공개)

비공개 상태 노출

  • 일반적인 안티 패턴으로 단위 테스트 목적으로만 비공개 상태를 노출하는 것이 있다.
  • 다시 말해 이 지침은 비공개로 지켜야 하는 상태를 노출하지 않고 식별할 수 있는 동작만 테스트 하라는 비공개 메서드 지침과 같다.

예를 들어, 클래스의 비공개 필드 상태를 변경하는 메서드를 테스트 하려면 어떻게 할까?

  • 이 경우 솔깃한 해결책은 필드를 공개로 변경하는 것이다
  • 그러나 이는 안티 패턴일 것이다.
  • 테스트는 제품 코드와 정확히 같은 방식으로 테스트 대상 시스템과 상호 작용해야 하고,
  • 특별한 권한이 따로 있으면 안된다.

그러면 어떻게 테스트 할까?

  • 제품 코드가 이 클래스를 어떻게 사용하는지를 대신 살펴보자
  • 나중에 제품 코드가 해당 필드를 사용하기 시작하면 공식적으로 SUT의 식별할 수 있는 동작이 되기에 테스트에서 해당 필드를 결합할 수도 있다.
테스트 유의성을 위해 공개 API 노출 영역을 넓히는 것은 좋지 않는 관습이다.

테스트로 유출된 도메인 지식

  • 도메인 지식을 테스트로 유출하는 것은 또하나의 안티패턴이다.
  • 예를 들어 덧셈 메서드를 테스트 하는 경우, 흔히 해당 더한 값을 메서드의 기대값으로 설정하는 경우이다.
  • 큰 문제는 아닌 것 처럼 보이지만, 준비 부분에 해당 알고리즘을 단순히 제품 코드에서 복-붙 하게 된다.
  • 이러한 테스트는 구현 세부 사항과 결합되는 또 다른 예이다.
  • 만약 알고리즘의 변경으로 테스트가 실패하면, 또 개발 팀은 원인을 파악하려고 노력하지 않고, 또 복-붙 하려 할 것이다.

그렇다면 어떻게 올바르게 테스트할 수 있을까?

  • 테스트를 작성할 때. 특정 구현을 암시하지 말라.
  • 알고리즘을 복제하는 대신 다음 예제처럼 결과를 테스트에 하드 코딩하자
    • 예를 들어서, 덧셈 메서드를 테스트 할 때, 기대 값을 1 + 2와 같이 적지 말고 3이라고 작성

코드 오염

코드 오염은 테스트에만 필요한 제품 코드를 추가하는 것이다.

코드 오염의 문제는 테스트 코드와 제품 코드가 혼재돼 유지비가 증가하는 것이다.

테스트 코드를 제품 코드베이스와 분리해야 한다.

구체 클래스를 목으로 처리하기

구체 클래스를 대신 목으로 처리해서 본래 클래스의 기능 일부를 보존할 수 있으며 때때로 유용하다.

그러나 단일 책임 원칙을 위반하는 중대한 단점이 있다.

“일부 기능만”을 지키려고 구체 클래스를 목으로 처리해야 하면, 이는 단일 책임 원칙을 위반하는 결과다.

이런 경우엔 각각의 기능을 쪼개어 객체를 분리하자.

시간 처리하기

많은 앱에는 현재 날짜와 시간에 대한 접근이 필요하다.

그러나 시간에 따라 달라지는 기능을 테스트하면 거짓 양성이 발생한다.

실행 단계의 시간이 검증 단계의 시간과 다를 수 있다.

이 의존성을 안정화하는 데는 세 가지 방법이 있다. 하나는 안티패턴이고, 나머지 두 가지 중 바람직한 방법이 있다.

  • 앰비언트 컨텍스트로서의 시간
    • 앰비언트 컨텍스트 패턴(정적 메서드를 통해 해당 값을 처리)을 사용하는 것
    • 여기서는 프레임워크의 내장 DateTime.now 대신, 코드에서 사용할 수 있는 사용자 정의 클래스로 처리하는 것을 말한다.
    • 앰비언트 컨텍스트는 제품 코드를 오염시키고 테스트를 더 어렵게 만든다.
    • 정적 필드는 테스트 간에 공유하는 의존성을 도입해서 해당 단위 테스트를 통합 테스트 영역으로 바꿔버린다.
  • 명시적 의존성으로서의 시간
    • 서비스 또는 일반 값으로 시간 의존성을 명시적으로 주입하는 것
    • 시간을 서비스(메서드)로 주입하는 것과 값으로 주입하는 것이 있는데, 값으로 주입하는 것이 더 낫다.
      • 하지만 아마 시간을 항상 일반 값으로 주입할 수는 없을 것이다….

요약

  • 단위 테스트를 가능하게 하려고 비공개 메서드를 노출시키면 테스트가 구현에 결합되고, 리팩터링 내성이 떨어진다. 따라서 비공개 메서드를 직접 테스트하는 대신, 식별할 수 있는 동작으로서 간접적으로 테스트하자.
  • 비공개 메서드가 너무 복잡해서 공개 API로 테스트할 수 없다면, 추상화가 누락됐다는 뜻이다. 비공개 메서드를 공개로 바꾸지 말고, 해당 추상화를 별도 클래스로 추출하자.
  • 비공개였던 상태를 단위 테스트만을 위해서 노출하지 말자. 테스트는 제품 코드와 같은 방식으로 대상 시스템과 상호 작용해야 한다. 어떠한 특권도 가지면 안된다.
    • 드물지만, 비공개 메서드가 클래스의 식별할 수 있는 동작에 속한 경우가 있다. 보통 클래스와 ORM 또는 팩토리 간의 비공개 계약을 구현하는 것이 여기에 해당한다.
    • 그래서 이 경우의 예외에 속하는 경우가 비공개 생성자 메서드를 공개 상태로 전환하는 것이다. 득실을 따져서 고민해 보자.
  • 테스트를 작성할 때 특정 구현을 암시하지 말자. 블랙박스 관점에서 코드를 검증하자.
  • 코드 오염은 테스트에만 필요한 제품 코드를 추가하는 것이다. 이는 테스트 코드와 제품 코드가 혼재되게 되고 제품 코드의 유지비가 증가되어서 안티패턴이다.
  • 기능을 지키려고 구체 클래스를 목으로 처리해야 하면, 단일 책임 원칙을 위반하는 결과다. 해당 클래스를 두 가지 클래스, 즉 도메인 로직이 있는 클래스와 프로세스 외부 의존성과 통신하는 클래스로 분리하자.

Uploaded by N2T

반응형

'Programming > OS' 카테고리의 다른 글

운영체제 정리  (0) 2024.02.23
[운영체제] 인터럽트  (0) 2023.05.23