ATDD

디우·2022년 6월 25일
0

우아한테크코스 레벨2, ATDD에 대해서 학습한 내용을 정리한다.

인수테스트 및 ATDD에 대해서는 우아한ATDD 를 통해서 보다 자세히 알아 볼 수 있을 것 같다.


인수 테스트 맛보기

인수테스트란 무엇일까? 인수테스트는 시나리오(사용자 스토리) 기반으로 기능을 테스트하는 것을 말한다.

이런 인수테스트의 도움을 받으면 다음과 같은 장점을 누릴 수 있다.

  • 배포 없이 받는 빠른 피드백
  • 새로운 팀의 도메인과 서비스 흐름 파악에 큰 도움이 된다.
  • 도메인 이해에 예상보다 짧은 시간이 소요된다.

그렇다면 인수 테스트가 없으면 어떨까? (without 인수 테스트)

  • 배포해서 기능이 제대로 동작하는지를 확인해야 한다.
  • 페이지에서 직접 테스트를 해야한다.
  • 수동으로 변경 사항을 확인해야 한다.

반면 인수테스트를 통해서는? (with 인수 테스트)

  • 배포없이 테스트로 대부분 검증이 가능하다.
  • 인수 테스트로 스펙을 표현할 수 있다.

빠른 피드백을 통해서

  • 새로운 문화들의 공통점은 빠른 피드백을 받는 방법이 있다는 것이다.
  • 든든한 지원군과 함께 코딩을 하는 느낌을 받을 수 있다.
  • 자신감을 주는 자동화된 테스트를 할 수 있다.

ATDD

Q. ATDD는 언제 필요할까?

우리는 요구사항을 잘못 이해한 경험이나 이제 뭘 어떻게 개발해야할지 길을 잃는 경험을 한 번쯤을 해보게 된다. 그게 규모가 작은 프로젝트라고 하더라도 말이다.
그러면 이 때 올바른 요구사항을 하도록 유도하는 것이 있다면 어떨까?
-> ATDD를 통해 효율적인 개발 프로세스를 경험함으로써 위와 같은 문제를 극복해볼 수 있다.

ATDD란?

테스트가 가능한 요구사항으로 소프트웨어를 개발하는 프로세스 (개발 방법론 중 하나)이다.

TDD는 write a failing unit test -> make the test pass -> refactor 의 흐름으로 진행된다.
TDD의 테스트는 작은 단위의 요구사항을 통해서 내가 앞으로 개발해 나가야할 것을 가리킨다.

ATDD는 어떤 특정 테스트를 가지고 구현을 하는 것인데, TDD는 작은 단위의 요구사항이라고 할 수 있지만 ATDD는 시나리오 형태의 요구사항이다. 즉, 시나리오 형태의 요구사항을 통해서 내가 앞으로 개발해 나가야할 것을 가리킨다.

"요구사항을 검증하는 테스트"로 소프트웨어를 개발하는 프로세스를 ATDD라고 할 수 있다.

ATDD를 하는 이유?

1. 생산성 증가

코드 작성 전에 테스트 작성을 통해서 생산성을 효과적으로 향상할 수 있다. 왜냐하면 요구사항이 명확해지기 때문이다.
또한 테스트로 명확하게 요구사항을 명시함으로써 이게 충족되는지 안되는지를 테스트를 돌림으로써 바로 확인할 수 있다.

2. 작업의 명확한 시작과 끝을 제시

3. 빠른 피드백
직접 구동을 시켜볼 필요가 없다. 즉, 서버를 띄우고 웹 페이지에서 클릭을 하면서 테스트를 해볼 필요가 없어진다. 따라서 빠른 피드백을 받아볼 수 있다.

4. 귀찮은 작업을 프로세스로 강제

ATDD Cycle

구체적인 ATDD Cycle

출처: Agile Acceptance Test Driven Development


인수 테스트

인수테스트(Acceptance Test)란 앞서 언급한 것과 같이 사용자 스토리를 검증하는 기능 테스트로, 사용자 스토리로 테스트할 시나리오를 결정한다.

테스트 종류

테스트에 종류는 다양하다.

  • 단위 테스트
    가장 작은 단위 테스트
    검증 대상: 구현한 부분, 단위를 검증
  • 통합 테스트
    단위와 단위가 잘 통합되는지를 테스트
    검증 대상: 각 단위들이 유기적으로 잘 동작하는지 검증
  • E2E 테스트
    End Point간 테스트
  • 인수 테스트
    사용자 스토리를 검증하는 기능 테스트

이렇게 테스트의 종류가 다양한데, 각 테스트는 무엇을 검증하려고 하는지 그 목적에 따라 나뉜다고 볼 수 있다.

인수테스트는 명세나 계약의 요구사항이 충족되는지 확인하기 위해 수행되는 테스트이다. 즉 이것이 인수테스트의 목적이다. 또한 앞서 잠깐 언급된 대로 작업의 시작과 끝을 명확히 제시한다. 즉, 작업을 종료 시켜도 되는지 검증하는 테스트로 이 테스트가 성공하면 작업이 끝이다를 명확히 할 수 있다.

우리는 이런 시나리오 기반의 인수테스트를 API 개발을 테스트하기 위해서 사용할 수 있다. API 레벨의 테스트는 다음과 같다.

@DisplayName("지하철역을 생성한다.")
@Test
void createStation() {
	
    // given
    Map<String, String> params = new HashMap<>();
    params.put("name", "강남역");
    
    // when
    ExtractableResponse<Response> response = RestAssured.given().log().all()
    	.body(params)
        .contentType(MediaType.APPLICATION_JSON_VALUE)
        .when()
        .post("/stations")
        .then().log().all()
        .extract();
    
    // then
    assertThat(response.statusCode()).isEqualTo(HttpStatus.CREATED.value());
    assertThat(response.header("Location")).isNotBlank();
}

이런 인수테스트는 테스트의 의도에 따라서 구현 방법이 달라질 수 있다.

블랙박스 테스트

인수테스트는 블랙 박스 테스트의 성격을 가지고 있다. 즉, 내부 구현에는 관심이 없다. 단지 우리는 인수테스트를 함으로써 요청과 응답 기반의 검증을 원한다.

블랙박스?

  • 클라이언트는 결과물의 내부 구현이나 사용된 기술을 기반으로 검증하기 보다는 표면적으로 확인할 수 있는 요소를 바탕으로 검증한다.
  • 실제 사용하는 상황의 시나리오를 바탕으로 요구사항을 작성한다.
  • 내부 구현이나 기술에 의존적이지 않는 블랙 박스 테스트

인수 테스트 만들기

  • SpringBootTest
    - 테스트에 사용할 ApplicationContext를 쉽게 지정하게 도와준다.

    • 기존 @ContextConfiguration의 발전된 기능이다.
    • SpringApplication에서 사용하는 ApplicationContext를 생성해서 작동시켜준다.
  • webEnvironment
    @SpringBootTestwebEnvironment 속성을 이용하여 테스트 서버의 실행 방법을 설정할 수 있다.

    • MOCK : Mocking된 웹 환경을 제공, MockMvc 를 사용한 테스트를 진행할 수 있다.
    • RANDOM_PORT : 실제 웹 환경을 구성, 랜덤한 포트번호 사용
    • DEFINED_PORT : 실제 웹환경을 구성(RANDOM_PORT와 동일) but 지정한 포트를 listen
    • NONE : 아무런 웹 환경을 구성하지 않는다.
  • RestAssured
    - REST-assured는 REST API의 테스트 및 검증을 단순화하도록 설계된 것으로 HTTP 작업에 대한 검증을 위한 풍부한 API를 제공한다.

    • 실제 web environement(Apache Tomcat)을 사용하여 테스트한다.
    • RestAssured 이외에도 WebClient, restTemplate, MockMVC 등이 존재한다.
  • MockMvc
    @SpringBootTestwebEnvironment.MOCK과 함께 사용 가능하며 모킹된 web environemnt (ex. tomcat) 에서 테스트한다.

  • WebTestClient
    @SpringBOotTestwebEnvironment.RANDOM_PORTDEFINED_PORT오 함께 사용하며 Netty를 기본으로 사용한다.

  • JsonPath
    $ 키워드 : 특정 필드만 꺼내오기 가능

  • 인수 텐스트 격리
    - Context Caching : SpringBootTest를 사용하여 테스트를 수행할 경우 context를 로드하는 시간이 많이 드는데 이를 위해 context를 캐싱하여 재사용하도록 동작한다.

    • @DirtiesContext : @DirtiesContext를 활용하여 캐싱 기능을 사용하지 않게 설정할 수 있으며 매번 context를 새로 구성하다보니 시간이 많이 걸린다는 단점이 있다.

classicist vs mockist

협력 객체가 있는 단위 테스트를 만들 때 어떻게 해야 할까?

  • Sociable & Solitary (통합과 고립)
    협력 객체를 실제 객체로 사용하는지 Mock 객체로 사용하는지에 따라 테스트 구현이 달라진다.

  • Test Double
    실제 객체 대신 사용되는 모든 종류의 객체에 대한 일반적인 용어이다.

    • Mockito
    • MockitoExtension
    • Fake 객체
    • Spring을 이용한 stub
  • classicist vs mockist
    - 협력 객체가 필요한 테스트는 단위테스트일까? 통합테스트일까?

    • classicist: 협력객체를 인정한다. 이 경우, inside out tdd로 작은 객체부터 tdd로 개발해나간다.
    • mockist : 협력 객체로부터 격리하기 위해 의존하는 객체들을 가짜 객체로 대체한다. 이 경우에는 outside tdd, 즉 큰 객체부터 tdd로 개발한다.
    • 이 둘에 대해서 정답은 정해져있지 않다.
profile
꾸준함에서 의미를 찾자!

0개의 댓글