💻 Backend

효과적인 단위 테스트(Unit Testing)

date
Dec 10, 2023
slug
GDG-Songdo-Backend1
author
status
Public
tags
Conference
summary
[GDG-Songdo-Backend] 효과적인 단위 테스트(Unit Testing)
type
Post
thumbnail
category
💻 Backend
updatedAt
Dec 11, 2023 03:16 PM
 

 
 

장동혁 / 효과적인 단위 테스트 (Backend)

⏰13:20
💡
Unit Testing - 블라디미르 코리코프 책 기반 발표
 

단위 테스트의 정의

  1. 마틴 파울러
    1. 작은 부분들에 집중한다.
    2. 테스트 툴을 사용해서 프로그래머들이 직접 작성한다.
    3. 다른 테스트 방식에 비해 확실히 빨라야한다.
  1. 블라디미르 코리코프
    1. 작은 코드 조각을 검증
    2. 격리된 방식으로 처리하는 자동화된 테스트
    3. 빠르게 수행되어야 한다.
 
단위 테스트에 대한 두 가지 관점
  1. Sociable Tests (고전파)
    1. 예전부터 테스트 하던 방식을 고수하자.
      1. // ServiceTest UserService userService = new UserService(); // 실제로 생성 TokenService tokenService = new UserService(); // 실제로 생성 login(loginId, password)
    2. 단위의 크기: 기능 단위 (1개 이상의 클래스, 의존관계가 있는 클래스들도 모두 생성)
    3. Mock(대역) 사용 범위: 외부 시스템에 대한 의존성이 있을 때
    4. 리팩토링 내성: 높다
      1. login(loginId, password) => LoginDto(loginId, password) login(LoginDto loginDto) // PASS
  1. Solitary Tests (런던파)
    1. 2010년쯤 런던에서 목킹하는 기술이 생겼고 목킹 툴을 적극적으로 활용하자고 함
      1. // ServiceTest UserService userService = Mock() TokenService tokenService = Mock() // Stubing 하여 목 객체 생성하여 테스팅 login(loginId, password)
    2. 코드 단위 (1개의 클래스, 의존관계가 있는 클래스들은 Mock 객체로 생성)
    3. Mock(대역) 사용 범위: 대부분의 의존성
    4. 리팩토링 내성: 낮다
      1. login(loginId, password) => LoginDto(loginId, password) login(LoginDto loginDto) // FAIL
 

좋은 단위 테스트의 4대 요소

  1. 버그 방지
      • 불필요한 테스트 코드의 작성들
  1. 리팩토링 내성
      • 거짓 양성 최소화
        • 거짓 양성은 테스트기의 품질 하락이 심함. 테스트기의 신뢰도를 떨어트림
          • 물론 거짓 음성도 최소화해야함
          • 거짓 양성: 실패하는 케이스가 왔는데, 정상으로 알려줌
          • 거짓 음성: 정상적인 케이스가 왔는데, 오류라고 알려줌
        • 테스터기는 테스터기 다워야 한다
        • 문제가 있을 땐 문제를 알려주고, 문제가 없을 땐 문제가 없다고 알려주는 것
  1. 빠른 피드백
      • 빠를수록 비용이 경제적이기 때문에 중요함
  1. 유지보수성
💡
얼마나 좋은 단위 테스트인가 = (버그 방지) x (리팩토링 내성) x (빠른 피드백) x (유지보수성) - 리팩토링 내성(양자적이다, 0 or 1 뿐이다. 깨지기 매우 쉽거나 또는 매우 어렵다는 뜻) - 버그방지← |통합 테스트|단위테스트 → 빠른 피드백 // TODO 사진 추가
 

Good Practice VS Bad Practice

  1. AAA(Arrange-Act-Assert), given-when-then
    1. 유지보수성 향상
      1. 코드가 직관적임
      def "login test"() { given: // given def logginId = "GDG Songdo" def password = "gdgsongdo123" when: // when def token = sut.login(loginId, password) def logout = sut.logout(token) then: // then token == "test token" }
  1. 하나의 테스트 메소드에는 하나만 검증한다.
    1. 버그 방지, 빠른 피드백 향상
    2. def "login test"() { given: def logginId = "GDG Songdo" def password = "gdgsongdo123" when: def token = sut.login(loginId, password) def logout = sut.logout(token) then: token == "test token" logout == true // 격리되지 않은 테스트 코드. 비추천 }
  1. 테스트 상호 의존성을 피해라
    1. 버그 방지, 빠른 피드백 향상
    2. private static int TEST_VARIABLE = 111; def "login test"() { TEST_VARIABLE = 111; given: def logginId = "GDG Songdo" (mId) def password = "gdgsongdo123" when: def token = sut.login(loginId, password) def logout = sut.logout(token) then: token == "test token" } def "logout test"() { TEST_VARIABLE = 222; // 비추천 given: def logginId = "GDG Songdo" def password = "gdgsongdo123" when: def token = sut.login(loginId, password) def logout = sut.logout(token) then: token == "test token" }
  1. 테스트 코드는 테스트 하려는 기능의 사용 설명서여야 한다.
  1. 테스트 코드는 제품 코드 작성 라이프 사이클에 포함된다.
    1. 망각 곡선
      1. 사람은 20분이지나면 42%를 까먹는다.
 

Q&A

  • 테스트 커버리지에 대해
    • 없는 것 보다는 있는 게 좋다.
      • 다만 과할 필요는 없다.
        • 만약 의미있는 테스트 코드를 다 작성했는데도 70%밖에 되지않는다면 커버리지를 높이기 위해 불필요한 테스트 코드를 생성시킬 수 있다. 50% 정도만 되어도 충분할 것 같다.
  • 고전파를 지향한다고 하셨는데 의존성 주입에 대해
    • 메인 기능 위주로 작성하되, 서브 기능 같은 경우는 목킹을 해서 작성하기도 함
  • 코드가 변경이 되면 테스트가 반드시 깨져야 한다 는 생각이 있는 팀이라면
    • 팀이 공감할 수 있게 스터디를 한다거나 공유 세션이 필요함. 함께 호흡하는 것이므로 팀과 함께 맞춰가는 게 더 중요함
 

참고링크

 
 

 

Comment

 
TDD를 실천하고 있는 회사에 다니고 있지는 않지만 요즘 테스트 코드에 대해 숙련도를 높이기 위해 해당 세션을 선택했다.
최근 이펙티브 자바, 이펙티브 코틀린, DDD 북 스터디를 하면서 책을 자주 읽고 있는데 금일 발표에서도 Unit Testing 이란 책을 기반으로 발표해주셨다. Unit Testing 책을 읽지 않았음에도 간결하고 시원한 발표에 책을 읽은 듯한 느낌이 들어 만족스러웠다. 최고 👍🏻
Good Practice VS Bad Practice 의 3번 항목은 실무에서 테스트 코드 짤 때 고민을 해봤던 요소이기도 하고, Q&A 에서 테스트 커버리지에 대해 높아야만 하는 지에 대한 고민도 있었는데 둘 다 답변을 받을 수 있어서 좋았다.
앞으론 단순히 테스트 코드를 짜기보다 어떻게 해야 더 좋은 테스트 코드가 될 수 있을 지 생각해보며 짤 수 있을 것 같다!