본문 바로가기
Programming/Clean Code

[ATDD] ATDD와 함께 클린 API로 가는 길 3기 - 오리엔테이션

by peter paak 2021. 3. 4.
728x90

TDD 사이클에서 무엇을 테스트 해야하는가에 대한 고민을 해결해줄 수 있다

  • 인수테스트 먼저 작성하고 단위테스트 생성
  • 인수테스트가 종료되면 단위테스트도 종료
  • 인수테스트는 요구사항 기반으로 작성되는 것이므로 목적이 분명해진다

어떻게 학습할 것인가?

  • TDD를 인수테스트와 함께 적용
  • 어떻게 작성하고 어떻게 관리할지 고민
  • TDD를 위한 테스트 도구도 학습
    • 도구 활용법이 익숙하지 않아서 어려움도 있다

인수테스트 사이클

학습

테스트 리펙토링

  • 유지보수 못하게 작성하면 말짱 도루묵
  • 어떻게 유지보수할것인가?
    • 사실 테스트 작성해놓고 많이 잊혀질 때가 많다
    • 테스트를 길고 복잡하게 작성하지 않는다
    • 가독성이 적으면 꼴도 보기 싫다

REST의 제약조건

  • 분산웹 환경에서 어떻게 효율적인 아키텍쳐를 만들 것인가 라는 고민에서 나옴
  • 각 환경에 필요한 요소에 맞게끔 REST의 제약조건을 사용한다면 환경에 최적화된 API를 설계할 수 있지 않을까

객체지향 생활체조

  • 이것을 생각하면서 구현을 하다보면 조금 더 객체지향적으로 사고를 하면서 코드를 작성할 수 있다
  • TDD 수업을 들었을 때는 별로 느끼지 못했지만 객체지향을 어느정도 이해한 시점에서는 정말 중요한 내용이라는 생각이 든다
    • 아래 모든 내용이 최대한 작은 객체로 만들어주는 역할을 한다고 생각한다

ATDD란

인수 받을 때 요구사항에 잘 맞게 개발하는 테스트

기술적인 용어없이 클라이언트가 읽기 좋은 문서로 만든다

  • BlackBox 테스트의 성질
  • 내부 구조의 동작원리를 테스트가 관여하지 않도록 한다
  • 그냥 요청과 응답으로 테스트가 잘 구현되었는지
  • 내부적으로 뭘 사용했는지 테스트한 것은 중요하지 않다

여러 직군의 이해를 돕기위한 테스트

  • 직군별로 이해관계가 다르다
  • 공통의 이해를 기반으로 하는 개발 방법론
  • 인수테스트
  • 공통적으로 이 기획을 우리가 모두 동의했으니 인수테스트를 진행하겠소

개발과정

  1. 인수조건 명시
    • 사용자스토리에 대한 설계가 이전에 있어야 한다 (여기서는 스킵)
    • 인수테스트가 만족하는 조건
    • 시나리오 기반의 표현방식을 사용 (보통의 사용법)
    • 체크 방식도 사용
    • Given When Then 사용
  2. 인수 테스트
    • 실제 요청 응답 환경과 유사하게 테스트
  3. 레거시 코드 리펙토링
    • 레거시에 대한 인수테스트를 작성하여 기존 기능을 보호
    • 그 상태에서 새로운 요구사항을 적용
    • 파악이 가능한 부분 먼저 단위테스트로 기능검증
    • 인수테스트 → 단위테스트
    • 무조건 이렇게 할 필요는 없지만 중요한 코드에 대해서는 보호를 하는것이 좋다

인수테스트 만들기

  • RestAssured가 요청 (클라이언트)
  • Mock으로 응답
  • 테스트 서버를 실행 (Random Port)
    • 테스트의 웹환경을 어떻게 구축할지 설정
  • RestAssured의 포트를 지정
  • 랜덤포트 사용
    • 여러 테스트가 실행될 때 포트 충돌이 생겨서

실습

  1. RestAssured 초기화
  2. 지하철역 생성 인수 테스트 만들기
  3. 지하철역 조회 인수 테스트 만들기
  4. JsonPath 사용
  5. Database Cleanup

1. RestAssured

  • 테스트 클라이언트 객체
  • 실제 요청을 보낸다
  • 그냥 웹페이지에서 요청보내는 것과 같다
  • 단점은 @Transactional 사용하지 못한다
RestAssured
    .given()
    .when()
    .then()

MockMvc

  • webEnvironment.MOCK으로 사용
  • 가짜 요청을 보낸다
  • 실제 http를 분석하는것이 아니라 path의 값을 보고 매칭되는 컨트롤러를 찾아서 처리한다
  • 하나의 트랜잭션 내에서 동작할 수 있다
  • Rollback 사용가능하다
  • 이걸로 인수테스트 하기도 한다

WebTestClient

  • Netty를 기본으로 한다
  • Webflux가 포함된 패키지를 사용한다
  • dispatcherServlet가 아닌 Webflux의 webinder환경에 적합한 테스트 객체이다
  • tomcat을 사용하는 것이 아닌 netty를 사용한다
  • 문서화 할 때 Spring Rest Docs를 사용할 때 톰캣 환경에서 Webtestclient를 사용하여 docs를 만들기 위해서는 추가적인 설정이 필요하다

RestAssured

  • 톰캣을 사용한다
  • 실제 웹 환경을 사용하여 테스트한다
  • 단 트랜잭션을 사용하지 못한다
  • ATDD 목적 상, 가장 바람직한듯하다

SpringBootTest

  • 조건에 부합하면 테스트를 돌릴 떄 똑같은 컨텍스트 사용
  • 컨텍스트 캐싱한다
  • 그래서 다음 테스트에 영향을 준다
  • 하나의 컨텍스트로 모든 테스트를 돌릴 수도 있다
  • 컨텍스트를 올리는데 비용이 크기 때문이다

2. Database Cleanup

테스트 격리

  1. @DirtiesContext
    • 컨텍스트를 초기화 해준다
    • 해당 컨텍스트를 새로 로드하도록 한다
      • 컨텍스트 캐싱을 막는다
      • 그래서 매번 컨텍스트를 구성하다보니 시간이 많이 걸림
  2. Repository의 초기화
    • 인수테스트에 사용하는 Repository를 알 수 밖에 없다
    • 해당 테스트가 특정 Repository에 강하게 결합되어 있다
  3. @Transactional
    • RestAssured에서는 Transactional 사용불가
  4. 쿼리를 이용한 초기화
    • 최대한 블랙박스에 가까운 설정인 것 같다
        @Transactional
        public void execute() {
              // DB로 업데이트
          entityManager.flush();
          entityManager.createNativeQuery("SET REFERENTIAL_INTEGRITY FALSE").executeUpdate();

          for (String tableName : tableNames) {
                      // 테이블의 모든 row 삭제
              entityManager.createNativeQuery("TRUNCATE TABLE " + tableName).executeUpdate();
                      // ID 컬럼을 1로 초기화
              entityManager.createNativeQuery("ALTER TABLE " + tableName + " ALTER COLUMN ID RESTART WITH 1").executeUpdate();
          }

          entityManager.createNativeQuery("SET REFERENTIAL_INTEGRITY TRUE").executeUpdate();
        }

API 설계

  • API 설계와 인수테스트는 밀접한 관계
  • produce
    • 특정 헤더 조건에 따라 해당 메소드가 동작하도록 할 수 있다

픽스쳐

  • setUp에 공통적으로 사용되는 given 절을 넣어줌
  • 가독성 증가
  • given절의 모임

간단한 성공 케이스 우선 작성

  • 간단한 성공 케이스 작성
  • 테스트가 동작하면 실제 구조에 대해 더 좋은 아이디어를 얻을 수 있음

When/Then/Given

  • When을 먼저 작성
    • 내가 무엇을 검증할지 명확하다
  • Then
    • 어떤 결과를 기대하는지
  • Given
    • 자연스럽게 Given이 도출된다

인수테스트 리팩터링

  • 각 테스트를 메소드로 묶어서 사용
    • 문서화 처럼 보일 수 있다
    • 재사용 가능
    • 너무 길면 ignore 혹은 Disable 처리하고 돌리게 된다
  • 테스트의 가독성은 중요하다
    • 가독성이 좋지않으면 방치될 수밖에 없음
    • 그러면 변경 사항에 대해 수정하기도 힘들다
    • 가독성이 좋으면 기능의 스펙을 나타낼 수 있다
    • 최대한 쪼개라
    • 그렇지 않으면 @Ignore과 @Disable로 범벅이 될 것이다
  • 중복 제거 방법
    • 메서드 분리
    • CRUD 추상화
      • 어떻게??
    • Cucumber 혹은 JBehave 사용

질문

  • 인수테스트와 통합테스트의 차이점
  • Stub을 만드는 방법
  • 단위테스트 인수테스트 통합테스트를 나누는지
  • BDD와의 차이점
    • TDD가 테스트코드 기반으로 개발
    • 테스트를 내가 구현할 요구사항으로 생각을 해보자
    • TDD 요구사항이 테스트
    • BDD 요구사항이 행위 (행위 중점, 범위가 넓다)
    • ATDD 요구사항이 인수테스트 (행위를 구체화해서 인수테스트)
    • 명세를 정의하고 개발해나간다에서 같은 맥락
  • 인수의 주체는?
    • 클라이언트
    • 이거 만들어주세요 하는 고객
  • 기존에 데이터가 있어서 중복에 대해 에러를 발생시키는 경우 어떻게 테스트
    • given절을 사용
    • 하나의 테스트 메소드에서 그냥 요청을 두번 보낸다
  • 인수테스트 단위테스트 구분
    • DM 보내자...
  • RestAssured에서는 @Transactional을 사용하지 못한다
  • CI/CD에서는 프로덕션인데
    • 테스트는 별도의 프로파일사용
    • 테스트용 DB를 따로 구축한다
  • 서버를 구동하면 테스트시간이 길어지는데 줄일 수 있는 방법이 있을까요?
  • 테스트 명은 어떻게 작성하나요

참고자료

728x90