///
Search
Duplicate
📗

[요약] 이펙티브 소프트웨어 테스팅

중요한 챕터만 요약

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. 읽기 쉬워야 하는 코드는 제품 코드가 아니라 테스트 코드다

어차피 제품 코드는 복잡하다. 여러 곳에 의존하고 있어서 의도를 파악하기도 어렵다. 테스트 코드가 있다면 제품 코드를 파악하기 쉬울 것이다.
그렇기 때문에 읽기 쉽게 작성해야 하는 코드는 제품 코드가 아니라 테스트 코드다.

10. 테스트는 쉽게 수정할 수 있어야 하고 진화할 수 있어야 한다.

마무리