[ 박우빈 Practical Testing #3 ] 더 나은 테스트를 작성하기 위한 Tip (3)

김수호·2024년 6월 19일
0
post-thumbnail

① 한 문단에 한 주제
② 완벽하게 제어하기
③ 테스트 환경의 독립성을 보장하자.
④ 테스트 간 독립성을 보장하자.
⑤ 한 눈에 들어오는 Test Fixture 구성하기
⑥ Test Fixture 클렌징

⑦ 테스트 수행도 비용이다. 환경 통합하기
⑧ private 메서드의 테스트
⑨ 테스트에서 필요한 메서드가 생겼는데 프로덕션 코드에서는 필요 없는 경우


⑤ 한 눈에 들어오는 Test Fixture 구성하기

  • Test Fixture
    • 테스트를 위해 원하는 상태로 고정시킨 일련의 객체.
    • 테스트 하고자 하는 환경을 조성하기 위해 given 절에서 생성하는 객체들을 의미한다.
      • given 절을 구성할 때 생성하는 모든 객체들을 의미한다고 생각하자.
      • 테스트 목적, 테스트 환경을 위해서 원하는 상태값으로 고정시킨 객체들.
    • 참고) Fixture: 고정물, 고정되어 있는 물체
  • Test Fixture 를 구성할 때의 4가지 주의사항
    • ① 테스트 실행 직전 애노테이션을 사용해서 Test Fixture 를 구성하는 것(Setup)을 지양하자. ( 최대한 given 절에 명시해서, 단일 테스트 코드 내에서 모든 것들을 표현하자. )
      • 하나의 클래스에서 여러 테스트를 작성할 때, given 절의 데이터가 겹치는 경우는 빈번히 발생한다. 그래서 보통 @BeforeEach 등을 사용해서 공통의 Test Fixture 들을 정의하는데, 그렇게 되면 테스트간 결합도가 생기게 될 수 있다. 따라서 Test Fixture 들을 수정하거나 하는 경우, 다른 테스트에 영향을 줄 수 있으므로 사용을 지양하는게 좋다.
      • 뿐만 아니라 코드 상단에 @BeforeEach 등을 사용해서 Test Fixture 들을 정의하게 되면, 테스트에서 사용하는 Test Fixture 가 테스트 내부에 정의되어 있지 않고 파편화되어 있다보니, 문서로서의 테스트 역할을 하기가 어려워진다.
      • 참고) 그러면 @BeforeEach 등을 언제 사용할까?
        • 기준1) 각 테스트 입장에서 봤을 때, 아예 몰라도 테스트 내용을 이해하는 데에 문제가 없는가? 라는 질문을 던져보자.
          • 테스트에 직접적으로 필요는 없지만, 간접적으로 생성을 해야 하는 것들에 대해서는 @BeforeEach 등 Setup 절에서 생성할 수 있다.
          • ex) Brand 관련해서 테스트를 하는데 Brand 는 Category 생성이 필요한 경우. 그리고 해당 테스트 자체는 Category 를 전혀 알지 않아도 되는 경우
          • 테스트가 문서로서 기능하는 것에 전혀 지장이 없는가? 라는 질문을 던져보자.
        • 기준2) 값을 수정해도 다른 테스트에 영향을 주지 않는가? 를 만족하는지 확인하자.
        • 두 기준을 만족하는 경우는 사용해도 된다. 그렇지 않다면 given 절이 길어지더라도, given 절에 명시하는 것이 좋다. ( 문서로서의 테스트를 염두하자. )
      • 참고)
        • @BeforeAll: 테스트 클래스 전체 실행 전에 무언가 작업을 하도록 적용
        • @BeforeEach: 매 테스트 메서드 실행 전에 무언가 작업을 하도록 적용
        • @AfterAll: 테스트 클래스 전체가 끝나고 나서 무언가 작업을 하도록 적용
        • @AfterEach: 매 테스트 메서드 실행 이후에 무언가 작업을 하도록 적용
    • given 절의 대상이 되는 데이터를 data.sql 등을 사용해서 미리 Setup 해서 생성해두는 것을 지양하자. ( given 데이터를 트래킹하기가 어려움 )
      • 이것 또한 위 조언 ① 에서 처럼 파편화가 발생하게 된다.
      • 테스트와 관련된 given 절의 Test Fixturedata.sql 에서 파편화되어 관리되는 것이다. 따라서 무엇을 테스트하는지를 파악하는 것이 어려워질 수 있다.
      • 또한, 프로젝트가 커질수록 data.sql 의 쿼리가 점차 많아질 것이다. 그러면, 점점 이 data.sql 자체에 대한 관리 포인트도 늘어나게 된다. (유지보수 포인트가 되기도 한다.)
    • ③ 메서드로 뽑을 때 필요한 파라미터만 명시해서 생성한다.
      • given 절에서 Test Fixture 를 구성할 때, builder 나 생성자를 사용해서 생성하는 경우, 코드가 길어지니까 테스트 클래스 내에서 메서드로 분리하고 호출해서 생성하는 경우가 많이 있다. 이때, 메서드 파라미터에는 테스트에서 꼭 필요한 필드 정보로만 전달되도록 하는 게 좋다. ( 테스트와 상관없는 것들은 메서드 내부에서 아무런 값으로 세팅하자. )
      • 이 테스트에서는 어떤 데이터를 기준으로 무엇을 볼건지를 명시적으로 알수있도록 하는게 좋다.
    • ④ 테스트 클래스마다 given 절에서 필요한 Test Fixture 가 공통되는 부분이 많은 경우, 그런 부분을 공통적으로 호출해서 생성하기 위해, 별도의 추상 클래스 등을 만들어서 builder 를 한 데 모아 구성해서 사용하는 것은, 오히려 먼 미래에 관리 포인트와 복잡도가 늘어나고, side effect 가 발생할 수 있다. (지양하자.)
      • 실무에서는 하나의 엔티티에 수십개의 필드가 있을 수 있다.

 

⑥ Test Fixture 클렌징

  • 트랜잭션 롤백 방식
  • deleteAllInBatch() / deleteAll()
    • deleteAllInBatch()
      • delete from tableName
      • 참고) 삭제 순서를 고려해야 한다. (외래키 조건이 걸려있거나 하는 등)
    • deleteAll()
      • select * from tableName
      • delete from tableName where id = ?
      • 참고) 테이블을 먼저 조회한 다음에 조회된 데이터를 기준으로 건건이 삭제가 진행된다. (삭제시 해당 테이블과 매핑되는 테이블의 데이터도 전부 조회해서 건건이 삭제해준다. )
        • 쿼리가 많아지고, 조회 결과 갯수만큼 삭제 쿼리를 실행하고, 중간에 매핑되는 테이블이 있다면 그 테이블도 건단위로 삭제하는 작업을 진행한다.
        • 테스트도 비용이다. 따라서 deleteAllInBatch 보다는 시간적으로 비용이 소요된다.
      • 참고) 엔티티의 관계 설정에 따라서 삭제 순서를 고려해야 한다.
  • 트랜잭션 롤백 방식(@Transactional)을 주로 사용하되, 그 외 수동으로 클렌징 해야 하는 경우는, deleteAll 보다는 deleteAllInBatch 를 사용하는 편이 낫다.

 

✔️ 정리

  • Test Fixture
    • Given 절에 테스트 환경을 조성할 때, 테스트와 직접적인 관련이 있는것과 없는것이 있을때, 없는 것은 Setup() 절에서 구성하면 좋다.
      • Setup() 절에서 무분별하게 전부 구성하면, (마치 공유변수를 쓰는 것 과 같은) 테스트가 결합도가 생길 수 있다.
    • data.sql 과 같이 파일로 관리하는 것도, 문서로서 테스트를 읽는데 방해가 되기 때문에 지양하자. (파편화)
  • Test Fixture 클렌징
    • @Transactional 자동 롤백을 사용하자.
      • 주의) 프로덕션 코드에 @Transactional 이 안걸려있더라도, 테스트에서 걸려있기 때문에, 프로덕션 코드 작성시 주의해야 한다.
    • @Transactional 로 처리하기 어려운 경우 수동으로 해야할 때가 있다. 그런 경우 deleteAll() , deleteAllInBatch() 를 사용하자.
      • 사용시 둘의 차이점을 명확히 알자.
      • 일반적으로 deleteAll() 보다는 deleteAllInBatch() 를 사용하는 편이 비용면에서 더 낫다.

강의를 듣고 정리한 글입니다. 코드와 그림 등의 출처는 박우빈 강사님께 있습니다.

profile
현실에서 한 발자국

0개의 댓글