728x90
TDD 사이클에서 무엇을 테스트 해야하는가에 대한 고민을 해결해줄 수 있다
- 인수테스트 먼저 작성하고 단위테스트 생성
- 인수테스트가 종료되면 단위테스트도 종료
- 인수테스트는 요구사항 기반으로 작성되는 것이므로 목적이 분명해진다
어떻게 학습할 것인가?
- TDD를 인수테스트와 함께 적용
- 어떻게 작성하고 어떻게 관리할지 고민
- TDD를 위한 테스트 도구도 학습
- 도구 활용법이 익숙하지 않아서 어려움도 있다
인수테스트 사이클
학습
테스트 리펙토링
- 유지보수 못하게 작성하면 말짱 도루묵
- 어떻게 유지보수할것인가?
- 사실 테스트 작성해놓고 많이 잊혀질 때가 많다
- 테스트를 길고 복잡하게 작성하지 않는다
- 가독성이 적으면 꼴도 보기 싫다
REST의 제약조건
- 분산웹 환경에서 어떻게 효율적인 아키텍쳐를 만들 것인가 라는 고민에서 나옴
- 각 환경에 필요한 요소에 맞게끔 REST의 제약조건을 사용한다면 환경에 최적화된 API를 설계할 수 있지 않을까
객체지향 생활체조
- 이것을 생각하면서 구현을 하다보면 조금 더 객체지향적으로 사고를 하면서 코드를 작성할 수 있다
- TDD 수업을 들었을 때는 별로 느끼지 못했지만 객체지향을 어느정도 이해한 시점에서는 정말 중요한 내용이라는 생각이 든다
- 아래 모든 내용이 최대한 작은 객체로 만들어주는 역할을 한다고 생각한다
ATDD란
인수 받을 때 요구사항에 잘 맞게 개발하는 테스트
기술적인 용어없이 클라이언트가 읽기 좋은 문서로 만든다
- BlackBox 테스트의 성질
- 내부 구조의 동작원리를 테스트가 관여하지 않도록 한다
- 그냥 요청과 응답으로 테스트가 잘 구현되었는지
- 내부적으로 뭘 사용했는지 테스트한 것은 중요하지 않다
여러 직군의 이해를 돕기위한 테스트
- 직군별로 이해관계가 다르다
- 공통의 이해를 기반으로 하는 개발 방법론
- 인수테스트
- 공통적으로 이 기획을 우리가 모두 동의했으니 인수테스트를 진행하겠소
개발과정
- 인수조건 명시
- 사용자스토리에 대한 설계가 이전에 있어야 한다 (여기서는 스킵)
- 인수테스트가 만족하는 조건
- 시나리오 기반의 표현방식을 사용 (보통의 사용법)
- 체크 방식도 사용
- Given When Then 사용
- 인수 테스트
- 실제 요청 응답 환경과 유사하게 테스트
- 레거시 코드 리펙토링
- 레거시에 대한 인수테스트를 작성하여 기존 기능을 보호
- 그 상태에서 새로운 요구사항을 적용
- 파악이 가능한 부분 먼저 단위테스트로 기능검증
- 인수테스트 → 단위테스트
- 무조건 이렇게 할 필요는 없지만 중요한 코드에 대해서는 보호를 하는것이 좋다
인수테스트 만들기
- RestAssured가 요청 (클라이언트)
- Mock으로 응답
- 테스트 서버를 실행 (Random Port)
- 테스트의 웹환경을 어떻게 구축할지 설정
- RestAssured의 포트를 지정
- 랜덤포트 사용
- 여러 테스트가 실행될 때 포트 충돌이 생겨서
실습
- RestAssured 초기화
- 지하철역 생성 인수 테스트 만들기
- 지하철역 조회 인수 테스트 만들기
- JsonPath 사용
- 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
테스트 격리
- @DirtiesContext
- 컨텍스트를 초기화 해준다
- 해당 컨텍스트를 새로 로드하도록 한다
- 컨텍스트 캐싱을 막는다
- 그래서 매번 컨텍스트를 구성하다보니 시간이 많이 걸림
- Repository의 초기화
- 인수테스트에 사용하는 Repository를 알 수 밖에 없다
- 해당 테스트가 특정 Repository에 강하게 결합되어 있다
- @Transactional
- RestAssured에서는 Transactional 사용불가
- 쿼리를 이용한 초기화
- 최대한 블랙박스에 가까운 설정인 것 같다
@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