[CS] Test Code의 모든 것

Coodori·2023년 4월 4일
0

CherishU

목록 보기
22/29

문제 발생

해당 시리즈 10번에 글을 썼지만 조금 부족하다는 느낌이 들어서 새롭게 정리를 하려고한다.

크게 3가지를 보려고하는데

  1. 통합테스트와 단위테스트의 차이
  2. TDD와 BDD의 차이
  3. Mockito 와 BDDMockito 차이(추가 사용법)
  4. Controller 단위 테스트 중 json 검증

이렇게가 앞으로 내가 테스트코드를 작성하는 습관을 가질 때 알고 있으면 좋을 듯한 지식 인 것 같았다.(3번은 궁금하기도 했고!)

그래서 해당 지식들에 정리하고 테스트 코드를 작성하는 습관을 가져보려고한다.

1. 통합테스트와 단위 테스트의 차이

  1. 단위테스트
    Unit Test 하나의 모듈을 기준으로 독립적으로 진행되는 가장 작은 단위의 테스트이다. 여기서 모듈은 애플리케이션에서 작동하는 하나의 기능 또는 메소드로 이해할 수 있다
  • 해당 부분만 독립적으로 테스트하기에 어떤 코드를 리팩토링하여도 문제여부를 빠르게 확인가능
  • 가장 확실하고 문제를 파악할 수 있음

좋은 단위 테스트의 4대 요소는 다음과 같다.

  • 회귀 방지 (Protection against regressions)
  • 리팩터링 내성 (Resistance to refactoring)
  • 빠른 피드백 (Fast feedback)
  • 유지보수 용이성 (Maintainability)

결론
1. 1개의 테스트 함수에 대해 assert를 최소화 해라
2. 1개의 테스트 함수는 1가지 개념만을 테스트해라

Spring Test : @WebMvcTest()(Controller) @ExtendWith(MockitoExtension.class) (Service)
사용되는 것: @Mock, @InjectMock

  1. 통합테스트
    통합 테스트(Integration Test)는 모듈을 통합하는 과정에서 모듈 간의 호환성을 확인하기 위해 수행되는 테스트이다.

일반적으로 애플리케이션은 여러 개의 모듈들로 구성이 되고, 모듈들끼리 메세지를 주고 받으면서(함수 호출) 기능을 수행한다. 그렇기에 통합된 모듈들이 올바르게 연계되어 동작하는지 검증이 필요한데, 이러한 목적으로 진행되는 테스트가 통합 테스트이다.

해당 테스트는 해당 프로그램의 전체적인 동작과정을 확인합니다.
실제 객체를 주입하고 다른 곳에서 에러가 발생하면 함께 에러가 발생하게 됩니다.

Spring Test : @SpringBootTest
사용되는 것: @Autowired

고로 단위 테스트와 통합 테스트가 서로 확인하려는 목적이 다르니 따로 구현을 해야한다.

보통은 작성한 로직이 맞는지 확인을 해야해서 단위테스트를 기본적으로 작성한다.

각각 테스트별 비율이다.

  • E2E — 10%
  • Integration — 20%
  • Unit — 70%

평소의 나는 통합테스트 중심의 테스트 코드를 짰었고 자료들을 찾아보면서 단위 테스트의 중요성과 개발 생산성에 대해 고려하게 되었다.

2. TDD와 BDD의 차이

BDD

BDD는 TDD에서 착안한 방법론 으로 행위 주도 개발(Behavior-Driven Development)을 말한다.

테스트 대상의 상태의 변화를 시나리오를 기반(Narrative)으로 테스트하는 패턴을 주로 사용한다. 이 때 권장하는 행동 패턴은 Given, When, Then 구조이다.

해당 패턴은 어떤 상태에서(Given) 어떤 행동을 했을 때(When) 어떤 결과가 되는 지(Then)를 테스트한다.

그렇다면 TDD가 어떤 방법론이기에 해당 방법론에서 착안을 하였을까?

TDD

  • TDD란 Test Driven Development의 약자로 '테스트 주도 개발'이라고 한다. 반복 테스트를 이용한 소프트웨어 방법론으로, 작은 단위의 테스트 케이스를 작성하고 이를 통과하는 코드를 추가하는 단계를 반복하여 구현한다.

정보처리기사에서 공부를 했었다.

개발주기
실패하는 테스트코드 작성 -> 성공시키기 위한 실제 코드 작성 -> 중복 코드 제거 등 리팩토링

여기서 핵심은 테스트 코드가 우선시 되고 우리가 해당 코드에 맞게 개발을 하는 것이다.

하지만 우리의 실 개발에서는
코드작성 -> 테스트 코드 검증 -> 필요시 리팩토링 으로 진행이 된다.

왜냐하면
1. 생산성 저하
왜냐하면 처음부터 2개의 코드를 짜야하고 중간중간 테스트를 하면서 고쳐나가야하기 때문이다.
그리고 품질보다는 납기일이 준수되기 때문에 좋은 방법임에도 불구하고 적게 실 사용이 된다.

해당 부분에서 관련된 문서 TDD는 죽었다라는 글도 보게 되었다.
또한 해당 글에서 TDD의 추가적으로 궁금한점을 알게되었다.
TDD는 버그를 박멸해주나요?

  1. TDD는 이렇게 해야된다는 이미지/틀이 있다.
  • 반드시 툴(단위 테스트 프레임워크)을 써서 개발해야 된다. 라고 생각한다.
  • 이러한 규칙에 얽매이는 것은 애자일 방식이 아니다.

하지만 그럼에도
TestCode가 없는 프로그램은 신뢰하기 어려우므로, TestCode를 작성하는 것이 좋다.

  • 코드가 내 손을 벗어나기 전에 가장 빠르게 피드백 받을 수 있다.
  1. TestCode를 잘 작성하려면, TestCode를 작성하기 좋은 Code를 작성해야 한다.
  2. Test하기 좋은 Code를 작성하려면, OOP적인 개발이 필요하다.

3. Mockito 와 BDDMockito 차이(추가 사용법)

궁금점을 가지게 된 계기

단위 테스트를 작성하다가 어떤 코드는 doReturn().when()

doReturn(new User(request.getEmail(), encryptedPw, UserRole.ROLE_USER)).when(userRepository)
        .save(any(User.class));

어떤 코드는 given().willReturn()

given(memberService.findById(1L)).willReturn(Optional.of(member));

라고 적는 것을 보았다.
직접 코드를 쳐보니

이렇게 두개가 다른 클래스의 메소드였다.

1. Mockito 와 BDDMockito 차이

Mockito란?

개발자가 동작을 직접 제어할 수 있는 Mock 객체를 지원하는 프레임워크이다.
@Service 클래스 부터 다양한 클래스들이 의존성을 가지게되어 단위 테스트 작성이 어려운데 이를 해결하기 위해 가짜 객체를 넣어주고 테스트를 하는 것이다.

사용하는 방법
@Mock: Mock 객체를 만들어 반환해주는 어노테이션
@Spy: Stub하지 않는 메서드들은 원본 그대로 사용하는 어노테이션

@InjectMocks: Spring 통합테스트의 @Autowired 같은 어노테이션인데 위의 두가지 어노테이션으로 만들어진 객체를 주입해주는 어노테이션
@ExtendWith(MockitoExtension.class) : Mockito와 JUnit을 결합시켜주는 어노테이션

  • 사용코드
@DisplayName("회원 가입") 
@Test void signUp() { 
// given 
final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); 
final SignUpDTO signUpDTO = signUpDTO(); 
final String encryptedPw = encoder.encode(signUpDTO.getPw()); 

// when 
doReturn(new User(signUpDTO.getEmail(), encryptedPw, UserRole.ROLE_USER)).when(userRepository).save(any(User.class)); 
final User user = userService.signUp(signUpDTO); 

// then 
assertThat(user.getEmail()).isEqualTo(signUpDTO.getEmail()); 
assertThat(encoder.matches(signUpDTO.getPw(), user.getPw())).isTrue(); 

// verify 
verify(userRepository, times(1)).save(any(User.class)); 
verify(passwordEncoder, times(1)).encode(any(String.class)); 
}

해당 처럼 doReturn(), verify()를 사용한다.
나는 참고만 하고 넘어갔다.
왜냐하면 BDDMocktio가 있기 때문에!

BDDMockito

BDDMockito는 Mockito를 상속받았다.
보통 상속 받는 것에는 많은 이유가 있겠지만 하나는 더 사용하기 편리하고 가독성있는 코드를 제공하는 것이다.

기능도 동일하지만 위에서 설명한 BDD 구조로 쉽게 읽힐 수 있도록 도와준다.
고로 시나리오에 맞는 테스트 코드가 읽힐 수 있도록 도와준다.

해당 패턴은 어떤 상태에서(Given) 어떤 행동을 했을 때(When) 어떤 결과가 되는 지(Then)를 테스트한다.

이것 외에도 verify()를 then().should()로 바꿔줄 수도 있다.

4.Controller 단위 테스트 중 json 검증

Controller는 단위테스트를 하기 위해
@WebMvcTest를 작성해주어야하고 인증된 사용자가 접근할 API와 모두에게 열린 API를 구분기에 두 Controller 다 검증 대상에 넣어줬다.

Spring Security를 썼기 때문에 csrf()도 넣어주고 REST API 이기에 Content-Type도 정해주었다.

여기서 검증하는 부분이 인상 깊었는데

given에서 내가 반환하는 Dto다.
RestController 이기 때문에 Json으로 반환이 된다.

그러면 해당 값에 대한 검증은

.andExpect(jsonPath("$.grantType").value("Bearer"))
               .andExpect(jsonPath("$.refreshToken").value("abc"))
         .andExpect(jsonPath("$.accessToken").value("cde"));

로 하면 되는 것이였다.

해당 부분은 이후 테스트에 검증하는 부분에서 자주 쓰일 부분일 듯하여 추가로 작성해놓는다.

결론

TDD, BDD, 단위테스트, 통합테스트 추가로 하는 법까지 찾아보고 공부했다.
그리고 적용해보며 내가 개발한 코드가 과연 내가 원하는 대로 작동하는 지까지 확인을 직접하였다.

내가 짠 코드가 내가 원하는 방식대로 나오니 내 개발 성장을 직관적으로 확인하고 이후 에러나는 부분을 찾아서 미리미리 예방할 수 있었다.

이번 프로젝트 이후로 몸에 테스트코드를 쓰는 습관을 가져야겠다.
(비록 시간이 오래 걸리더라도 이후에 문제가 발생하여 찾는 것이 더 괴로우니깐... 몸에 익으면 자연스러워지겠지)

REFERENCE

http://clipsoft.co.kr/wp/blog/tddtest-driven-development-%EB%B0%A9%EB%B2%95%EB%A1%A0/
https://velog.io/@walker/TDDTestCode%EB%8A%94-%EC%99%9C-%ED%95%A0%EA%B9%8C
https://mangkyu.tistory.com/145
https://velog.io/@yyong3519/Mockito
https://velog.io/@lxxjn0/Mockito%EC%99%80-BDDMockito%EB%8A%94-%EB%AD%90%EA%B0%80-%EB%8B%A4%EB%A5%BC%EA%B9%8C

profile
https://coodori.notion.site/0b6587977c104158be520995523b7640

0개의 댓글