Skip to content

Latest commit

 

History

History
238 lines (145 loc) · 8.6 KB

테스트주도_개발_시작하기.md

File metadata and controls

238 lines (145 loc) · 8.6 KB

테스트 주도 개발 시작하기 [최범균 저]

책 읽고 기억하고 싶은 구절 메모 📖


picture 9

TDD 흐름

(반복) [ 테스트 -> 코딩 -> 리펙토링 ]

1. TDD는 기능을 검증할 수 있는 테스트를 먼저 작성한다.
- 테스트를 작성하는 과정에서 구현을 생각하지않는다.
2. 테스트를 통과시킬 만큼 코드를 작성한다.
- 테스트를 추가하고 그때까지 추가한 테스트를 통과할 만큼의 코드만 구현한다.
3. 리펙토링으로 마무리 하는 과정을 거친다.

TIP

  • 테스트코드가 추가되면서 검증하는 범위가 넓어지고 구현도 점점 완성된다.
  • 필요할 것으로 예측해서 미리 코드를 만들지 않는다.

즉, 이렇게 테스트가 개발을 주도해나간다.


주의 사항

1. 첫번째 테스트를 선택할때는 가장 쉽거나 가장 예외적인 상황을 선택해야한다.

  • ex) 모든 규칙을 충족하는 경우 조건을 충족하지 않는 경우
  • 쉬운 경우 -> 어려운 경우로 진행
  • 예외적인 경우 -> 정상인 경우로 진행

2. 초반에 복잡한 테스트부터 시작하면 안된다.

  • 이유는 해당 테스트를 통과시키기 위해서 한 번에 구현해야 할 코드가 많아진다.

3. 예외상황을 먼저 테스트하는 이유

  • 다양한 예외 상황은 복잡한 if-else 블록을 동반할 떄가 많다.
  • 예외 상황을 전혀 고려하지 않은 코드에 예외 상황을 반영하려면 코드의 구조를 뒤집거나 코드 중간에 예외 상황을 처리하기 위해 조건문을 중복해서 추가하는 일이 벌어진다.
    • 버그발생가능성 up, 코드복잡성 up

완급조절

  • 한번에 얼마만큼의 코드를 작성할 것인가? (교재 p.66 참조)
    1. 정해진 값을 리턴
    2. 값 비교를 이용해서 정해진 값을 리턴
    3. 다양한 테스트 추가하면서 구현을 일반화

하지만 단순한 로직이거나 TDD가 익숙해지면 1번 제외하고 바로 구현해도됨 그러나 한 번에 구현을 시도했는데 안 되면 한발물러서서 1번부터하자!


테스트가 가능한 설계

테스트가 어려운코드

  • 뒤에 어떻게 테스트가 가능하게 바꾸는지 알아봄 (p180 참조~)

1. 하드 코딩된 경로

  • (before) 하드코딩된 ip주소, 포트번호 포함

  • (after) 하드코딩된 상수를 생성자나 메서드 파라미터로 받기

    • 세터를 이용해서 교체 가능하게 함으로써 테스트가 쉬워짐

2. 의존객체를 직접생성

  • (after) 의존대상 주입받기

3. 정적메서드 사용

  • (after) 외부 라이브러리는 직접 사용하지 말고 감싸서 사용하기!

  • 대역으로 대체하기 어려운 외부 라이브러리가 있다면 외부 라이브러리를 직접 사용하지 말고 외부 라이브러리와 연동하기 위한 타입을 따로 만든다. 테스트 대상 트 대상 코드는 새로 분리한 타입을 사용함으로써 외부 연동이 필요한 기능을 쉽게 대역으로 대체할수있다.

4.실행 시점에 따라 달라지는 결과

  • 예시
    • ex1) LocalDate.now()와 같이 실행결과가 달라지는 경우가 있다.
      • 어제까지는 문제 없이 성공하던 테스트가 오늘은 깨질 수 가 있다.
      • ex2) Random을 이용해서 임의값을 사용하는 코드도 마찬가지 실행시점에 따라 달라진다.
  • (after) 테스트하고싶은코드를 분리하기

5. 역할이 섞여 있는 코드

  • p188

테스트 범위와 종류

1. 기능 테스트와 E2E(End to End) 테스트 

  • 기능 테스트는 사용자입장에서 시스템이 제공하는 기능이 올바르게 동작하는지 확인한다.

  • 이 테스트를 수행하려면 시스템을 구동하고 사용하는데 필요한 모든 구성요소 필요
    • ex, 회원가입 기능이 올바르게 동작하는지 확인하려면 웹서버,db,브라우저 필요
  • 모든구성요소를 논리적으로 완전한 하나의 기능으로 다룸

  • QA 조직이 주로 기능 테스트함
  • 예를들어, 회원가입 폼을 통해서 가입했을때 해당 정보로 로그인이 잘 되는지, 회원 정보를 조회할때 가입할때 입력한 데이터가 올바르게 표시되는지 등을 확인한다.

즉, 사용자와 동일한 방식으로 기능을 검증해야한다.


2. 통합테스트

  • 통합테스트는 시스템의 각 구성 요소가 올바르게 연동되는지 확인한다.
    • 기능테스트는 사용자입장에서 테스트
    • 통합 테스트는 소프트웨어의 코드를 직접테스트

  • ex)모바일앱
    • 기능테스트 - 앱을 통해 가입 기능 테스트
    • 통합 테스트 - 서버 회원가입코드를 직접 테스트

3. 단위테스트

  • 단위테스트는 개별 코드나 컴포넌트가 기대한대로 동작하는지 확인한다
    • 한 클래스나 한 메서드와 같은 작은 범위를 테스트한다.
    • 일부 의존 대상은 스텁이나 모의 객체등을 이용해서 대역으로 대체한다.
  • 차이정리 p199 참조

차이점 정리

1.  

  • 통합테스트는 db, 캐시 서버와 같이 연동대상 구성
  • 기능테스트는 모바일 앱을 폰에 설치할수도
    • 테스트 상황을 만들기위해 많은 노력필요
  • 단위 테스트는 테스트 코드를 빼면 따로 준비할것없다.

2. 

  • 통합테스트는 테스트 실행속도를 느리게하는 요인많다.(db연결, 소켓통신, 스프링 컨테이너 초기화)
  • 기능테스트는 추가로 브라우저나 앱을 구동하고 화면의 흐름에 따라 알맞은 상호작용해야함
  • 단위테스트는 서버 구동하거나 db 준비할필요 x -> 의존하는 기능을 대역으로 처리하면됨
    • 실행속도 빠름

3.

  • 통합테스트나 기능테스트로는 상황을 준비하거나 결과확인이 어렵거나 불가능할때가있다.
    • (특히, 외부 시스템연동)
    • 이런경우에는 단위테스트와 대역을 조합해서 상황을 만들고 결과확인해야함

WireMock을 이용한 REST 클라이언트 테스트

통합 테스트하기 어려운 대상이 외부 서버다.

이럴때 WireMock 을 사용하면 서버 API를 스텁으로 대체할수있다.

(P.204)


주의사항


1. 변수나 필드를 사용해서 기댓값 표현하지 않기

2. 두 개 이상을 검증하지 않기

3. 과도하게 구현 검증하지 않기

내부구현을 검증하는 것은 나쁜건아니다.

예를들어,

//PasswordChecker#checkPasswordWeak() 메서드 호출여부 검사
BDDMockito.then(mockPasswordChecker)
.should()
.checkPasswordWeak(Mockito.anyString());

단점

  • 구현을 조금만 변경해도 테스트가 깨질 가능성이 커진다.
  • 내부 구현은 언제든지 바뀔 수 있다. 따라서 테스트코드는 내부 구현보다 실행 결과를 검증 해야한다.

  • but, 이미 존재하는 코드에 단위 테스트를 추가하면 어쩔 수 없이 내부 구현을 검증해야 할 때도있다.
    • 기능이 정상적으로 동작하는지 확인할 수단이 구현 검증밖에 없다면 모의 객체를 사용해서
  • 테스트코드를 작성해야하지만 일단 테스트 코드를 작성한뒤에는 점진적으로 코드를 리팩토링해서 구현이 아닌 결과를 검증할 수 있도록 시도해야한다.
  • 그렇게 함으로써 향후에 사소한 구현 변경으로 인해 테스트가 꺠지는 것을 방지할 수 있고 또한 코드의 테스트 가능성도 높일 수 있다.

4. 셋업을 이용해서 중복된 상황을 설정하지않기

@BeforeEach
void setUp(){
	changeService = new ChangeUserService(memoryRepository);
           }

모든 테스트 메서드에 동일한 상황을 적용한다.

주의사항

  1. setUp을 하면 유지보수 힘들어질 수도 있다.
    • 나중에 코드를 볼때 메서드를 이해하기 위해서 위아래로 이동하면서 확인해야한다.
    • 테스트메서드가 자체적으로 검증하는 내용을 완전히 기술하고 있어야 좋다.
  2. 또한, 다른 메서드 결과에 영향미칠 가능성도 있다.