•
11장에서 다루는 내용
◦
비공개 메소드 단위 테스트
◦
단위 테스트를 하기 위한 비공개 메소드 노출
◦
테스트로 유출된 도메인 지식
◦
구체 클래스 목 처리
•
테스트에서 시간을 어떻게 다루는지 알아보고 비공개 메소드 단위 테스트, 코드 오염, 구채 클래스의 목 처리 등과 같은 안티 패턴을 설명하며 이를 피하는 방법도 알아본다.
1. 비공개 메소드 단위 테스트
•
비공개 메소드를 어떻게 테스트해야할까? 전혀 하지 말아야한다.
1.
비공개 메소드와 테스트 취약성
•
단위 테스트를 하고자 비공개 메소드를 노출하는 경우, 식별할 수 있는 동작만 테스트하는 것을 위반하게 된다.
•
비공개 메소드를 노출하면 테스트가 구현 세부 사항과 결합되고 결과적으로 리팩토링 내성이 저하된다.
•
비공개 메소드를 직접 테스트하는 것 대신 포괄적인 식별할 수 있는 동작으로서 간접적으로 테스트하는 것이 좋다.
2.
비공개 메소드와 불필요한 커버리지
•
비공개 메소드가 너무 복잡해서 식별할 수 있는 동작으로 테스트하기에 충분히 커버리지를 얻을 수 없는 경우가 있다.
•
식별할 수 있는 동작에 이미 합리적인 테스트 커버리지가 있다고 가정하면 다음 두 가지 문제가 발생할 수 있다.
◦
추상화가 누락되어있다. 비공개 메소드가 너무 복잡하면 별도의 클래스로 도출해야 하는 추상화가 누락되었다는 징후다.
◦
죽은 코드다. 테스트에서 벗어난 코드가 어디서도 사용되지 않는다면 삭제하는 것이 좋다.
2. 비공개 상태 노출
•
단위 테스트를 목적으로만 비공개 상태를 노출하는 것이다.
•
비공개 상태는 '구현 세부 사항'이기 때문에 단위 테스트에서 이것을 검증할 필요는 없다. 오히려 리팩토링 내성을 떨어뜨려 나쁜 테스트를 만들게 된다.
3. 테스트로 유출된 도메인 지식
•
도메인 지식이 테스트로 유출되는 것은 또 하나의 안티패턴이다.
•
이것은 도메인 지식이 바뀔 때 마다 코드를 수정해야하며, 이는 리팩토링 내성의 저하를 불러일으킨다.
4. 코드 오염
•
코드 오염이란 비즈니스에 필요없고 테스트에만 필요한 코드를 추가하는 것이다.
5. 구체 클래스를 목으로 처리하기
•
인터페이스를 이용해 목을 처리하는 예제를 살펴봤었다.
•
구체 클래스를 목으로 처리하는 것이 안티패턴인 이유는 뭘까?
•
일부 기능을 살리기 위해 구체 클래스를 목으로 처리한다면 이는 단일 책임 원칙을 위반하는 행위다.
6. 시간 처리하기
•
많은 어플리케이션에는 시간에 따라 달라지는 기능이 존재할 수 있다.
•
시간에 따라 달라지는 기능을 테스트하면 거짓 양성이 발생할 가능성이 크다.
•
따라서 이 부분을 테스트 하기 위해서는 프로덕트 코드 역시 수정되어야 하고, 테스트 코드 역시 수정되어야 한다.
•
명시적 의존성 형태로 시간을 주입하기
◦
LocalDateTime.now()와 같은 형식으로 생성된 시간은 테스트 할 때 거짓 양성이 나오기 쉽다.
▪
해당 값은 매번 바뀌는 것이고 테스트에서 예측할 수도 없기 때문이다.
▪
따라서 이것을 해결 하기 위해서는 시간에 의존적인 클래스에게 '시간 값을 가진 객체'를 주입해주는 방법으로 처리할 수 있다.
요약
•
단위 테스트를 가능하게 하고자 비공개 메서드를 노출하게 되면 테스트가 세부 구현 사항에 결합되게 된다. 그 결과 리팩토링 내성이 떨어진다.
•
비공개 메서드가 너무 복잡해서 공개 API로 테스트를 할 수 없다면, 추상화가 누락되었다는 뜻이다.
◦
비공개 메서드를 공개로 하지 말고, 복잡한 녀석을 별도의 클래스로 추출해서 추상화한다
•
비공개 상태를 단위 테스트를 위해 노출하지 마라.
◦
테스트 코드는 제품 코드와 같은 코드가 사용되는 방식을 이용해서 검증해야한다. 이것은 또한 코드 오염에 해당되기도 한다.
•
테스트를 작성할 때 특정 구현을 암시하지 마라.
◦
이것은 도메인 로직이 테스트 코드에 유출된 것이고, 리팩토링 내성을 떨어뜨린다.
•
코드 오염은 테스트에만 필요한 코드를 제품 코드에 포함하는 것을 의미한다.
◦
제품 코드 내에 다양한 코드가 산재되기 때문에 유지보수 비용이 증가한다.
◦
만약 테스트를 위한 코드가 반드시 포함되어야하면 인터페이스를 하나 추가하고, 그 인터페이스의 구현체를 생성하는 것으로 코드 오염을 최소화한다.
•
기능을 지키기 위해 구체 클래스를 Mock으로 처리해야 한다면, 이는 단일책임원칙(SRP)를 위반한 결과다.
◦
Mock으로 처리한다는 것은 외부 프로세스와 통신하는 역할을 하기 때문이다.
◦
만약 이런 문제가 있다면, 외부 프로세스 통신과 도메인 로직이 있는 클래스를 분할하라.
•
시간에 의존적인 테스트가 있다면, 제품 코드에서 시간값 객체를 주입받아 동작하는 형태로 리팩토링 한 후 테스트 코드를 작성한다.