중요한 챕터만 요약
Chapter 3. 구조적 테스트와 코드 커버리지
명세 기반 테스트
•
컴포넌트에서 명세는 어떻게 뽑아낼 수 있을까
•
경험과 창의성이 최대한 덜 필요하도록 어떻게 할 수 있을가
구조화 테스트
•
소스 코드 구조를 사용하여 테스트를 도출하는 것
•
커버리지 기준을 이해한다는 뜻
명세 기반 테스트를 구조화 테스트로 보강한다.
커버리지
•
조건, 분기, 경로 커버리지 등이 존재
조건/의사결정 커버리지 (MC/DC: Modified condition/Decision coverage)
•
가능한 모든 조합 대신 중요한 조합을 찾아낸다.
•
매개변수의 가능한 모든 조건은 적어도 한 번은 결과에 영향을 주어야 한다.
Chapter 6. 테스트 더블과 모의 객체
•
종속성을 너무 신경쓰지 말고 격리된 방식으로 테스트하는데 초점을 맞추자
•
구체적인 의존성과 함께 수행해야 하는 일들은 너무 느리거나, 너무 힘들거나, 너무 많은 일을 해야 한다.
테스트 더블
•
더 큰 제어권: 무엇을 해야 할지 쉽게 알려줄 수 있다.
•
빠르다.
•
더미
◦
사용되지 않는 객체
•
페이크 객체
◦
시뮬레이션하려는 클래스와 동일하게 실제로 동작하는 구현체
◦
but 단순화 되어있다.
•
스텁
◦
호출에 대한 응답을 제공 (구현체가 없다)
•
모의 객체
◦
메서드의 응답을 설정한다. (=스텁)
◦
상호작용을 저장하고 이를 단언할 수 있다.
▪
toHaveCalledTimes(1)
◦
유일하게 행동 검증이 가능하다. (다른 것들은 상태 검증)
•
스파이
◦
의존성을 감시한다.
◦
상호작용을 기록한다.
•
모의 객체가 대규모로 잘 동작하게 하려면 '계약'을 신경써서 설계해야 한다.
◦
계약이 잘 설계되고 안정적이라면 모의 객체 사용이 두렵지 않다.
•
테스트 대상 클래스에 대한 정보를 너무 많이 알게 된다.
◦
변경에 유연하지 않는다.
◦
테스트와 제품 코드 간의 결합도를 증가시킨다.
•
다음과 같은 경우에 모의 객체를 사용한다.
◦
느린 경우
◦
의존성이 외부 인프라와 통신하는 경우
◦
시뮬레이션이 어려운 경우
•
그 외,
◦
비즈니스 개념을 표현하는 엔티티 클래스.
◦
인스턴스를 만든다.
◦
네이티브 라이브러리와 유틸리티 메서드
•
테스트 더블을 사용하려면 시스템이 테스트 가능성을 가지도록 설계해야 한다.
•
실제 구현에 충실하게 테스트 더블을 구축하는 일은 어렵지만 그렇게 해야 한다.
•
고립성보다 현실성이 낫다.
◦
느린가?
◦
비결정적인가?
•
모의 클래스가 너무 많이 필요하다면 클래스가 제대로 설계되지 않았다는 것
•
상호작용 테스트보다 상태 테스트가 낫다.
◦
적절한 상호작용 테스트는 유용하다.
◦
발생하는 모든 상호작용을 검증하지 말자
Chapter 7. 테스트 가능성을 위한 설계
•
테스트 가능성: 테스트 대상 시스템이나 클래스, 메서드에 대해 테스트 코드 작성을 얼마나 쉽게할 수 있는지
•
테스트 가능성은 항상 고려해야 한다.
•
도메인 코드에서 인프라의 모든 복잡성을 추상화한다.
•
모듈(컴포넌트, hook) 이 제어가능한지 관찰가능한지
•
의존성 역전 원칙
◦
고수준 모듈은 저수준 모듈에 의존해서는 안된다.
◦
이 둘 모두는 추상화에 의존해야 한다.
◦
추상화는 세부사항에 의존하면 안 된다.
◦
세부사항은(구현) 추상화에 의존해야 한다.
•
원칙적으로 테스터는 private 메서드를 오로지 public 메서드를 통해서만 테스트해야 한다.
•
그러다가 배보다 배꼽이 더 커지는 그런 상황이라면, 아마 private 메서드는 현재 그 위치에 있어선 안 되는 메서드일 것이다.
•
하나의 역할을 하고 있을 것이며 몸담았던 클래스의 맥락을 빼고 별도 클래스로 분리되어 테스트 되어야 할 것이다.
Chapter 10. 테스트 코드의 유지 보수성을 위한 원칙
1. 테스트는 빨라야 한다.
•
변경할 때마다 피드백을 받을 수 있어야 한다.
2. 테스트는 응집력 있고 독립적이며 격리되어야 한다.
•
복잡한 테스트 코드는 무엇을 테스트하는 것인지 한눈에 볼 수 없을 뿐 아니라 유지보수하기도 어렵다.
•
단순하고 짧은 테스트를 작성하자
•
테스트를 격리해서 실행하든, 같이 실행하든 그 결과는 동일해야 한다.
◦
이전 테스트 결과에 영향을 받는 테스트 케이스는 신뢰도가 매우 떨어진다.
3. 테스트는 존재 이유가 있어야 한다.
•
버그를 재현했거나
•
문서화 하기 위한 목적일 수 있다.
•
모든 테스트는 유지보수 되어야 한다.
•
최소한의 수행횟수로 모든 버그를 감지할 수 있어야 한다.
•
완벽한 테스트 케이스를 만들 순 없겠지만 적어도 쓸모없는 테스트는 만들지 않는 것이 좋다.
4. 테스트는 반복 가능해야 하며 불안정하지 않아야 한다.
•
불안정한 테스트는 생산성을 해친다.
•
버그가 있다는 것인지 우연으로 깨진건지 알 수 없으므로 가치가 없다.
•
보통 다음과 같은 경우 불안정하다
◦
외부 자원이나 공유 자원에 의존하는 경우 (비동기식 대기)
◦
부정확한 타임아웃에 의존하는 경우
◦
동시성 이슈
◦
테스트 순서 의존성
5. 단언문은 탄탄해야 한다.
•
행위의 결과를 단언해야 하며
•
행위의 결과를 관찰할 수 없는 경우, 좋은 테스트를 작성할 수 없다. 리팩터링이 필요하다는 신호이다.
•
확인해야 하는 것을 전부 확인해야 한다,
6. 테스트는 행위가 변경될 경우, 깨져야 한다.
•
테스트를 통해 예상된 행위가 깨졌음을 알 수 있어야 한다.
7. 테스트는 단 하나의 명확한 이유로 실패해야 한다.
•
행위 하나만 테스트해야 하며 하나만 수행할 경우 실패 이유는 하나가 된다.
8. 테스트는 작성하기 쉬워야 한다
•
테스트를 작성하기 쉬운 환경도 중요하다
•
공부도 중요하다
•
의존성 관리도 중요하다
9. 읽기 쉬워야 하는 코드는 제품 코드가 아니라 테스트 코드다
•
어차피 제품 코드는 복잡하다. 여러 곳에 의존하고 있어서 의도를 파악하기도 어렵다. 테스트 코드가 있다면 제품 코드를 파악하기 쉬울 것이다.
•
그렇기 때문에 읽기 쉽게 작성해야 하는 코드는 제품 코드가 아니라 테스트 코드다.