💻 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 - 블라디미르 코리코프
책 기반 발표단위 테스트의 정의
- 마틴 파울러
- 작은 부분들에 집중한다.
- 테스트 툴을 사용해서 프로그래머들이 직접 작성한다.
- 다른 테스트 방식에 비해 확실히 빨라야한다.
- 블라디미르 코리코프
- 작은 코드 조각을 검증
- 격리된 방식으로 처리하는 자동화된 테스트
- 빠르게 수행되어야 한다.
단위 테스트에 대한 두 가지 관점
- Sociable Tests (고전파)
- 예전부터 테스트 하던 방식을 고수하자.
- 단위의 크기: 기능 단위 (1개 이상의 클래스, 의존관계가 있는 클래스들도 모두 생성)
- Mock(대역) 사용 범위: 외부 시스템에 대한 의존성이 있을 때
- 리팩토링 내성: 높다
// ServiceTest UserService userService = new UserService(); // 실제로 생성 TokenService tokenService = new UserService(); // 실제로 생성 login(loginId, password)
login(loginId, password) => LoginDto(loginId, password) login(LoginDto loginDto) // PASS
- Solitary Tests (런던파)
- 2010년쯤 런던에서 목킹하는 기술이 생겼고 목킹 툴을 적극적으로 활용하자고 함
- 코드 단위 (1개의 클래스, 의존관계가 있는 클래스들은 Mock 객체로 생성)
- Mock(대역) 사용 범위: 대부분의 의존성
- 리팩토링 내성: 낮다
// ServiceTest UserService userService = Mock() TokenService tokenService = Mock() // Stubing 하여 목 객체 생성하여 테스팅 login(loginId, password)
login(loginId, password) => LoginDto(loginId, password) login(LoginDto loginDto) // FAIL
좋은 단위 테스트의 4대 요소
- 버그 방지
- 불필요한 테스트 코드의 작성들
- 리팩토링 내성
- 거짓 양성 최소화
- 거짓 양성은 테스트기의 품질 하락이 심함. 테스트기의 신뢰도를 떨어트림
- 물론 거짓 음성도 최소화해야함
거짓 양성: 실패하는 케이스가 왔는데, 정상으로 알려줌
거짓 음성: 정상적인 케이스가 왔는데, 오류라고 알려줌
- 테스터기는 테스터기 다워야 한다
- 문제가 있을 땐 문제를 알려주고, 문제가 없을 땐 문제가 없다고 알려주는 것
- 빠른 피드백
- 빠를수록 비용이 경제적이기 때문에 중요함
- 유지보수성
얼마나 좋은 단위 테스트인가 = (버그 방지) x (리팩토링 내성) x (빠른 피드백) x (유지보수성)
- 리팩토링 내성(양자적이다, 0 or 1 뿐이다. 깨지기 매우 쉽거나 또는 매우 어렵다는 뜻)
- 버그방지← |통합 테스트|단위테스트 → 빠른 피드백 // TODO 사진 추가
Good Practice VS Bad Practice
- AAA(Arrange-Act-Assert), given-when-then
- 유지보수성 향상
- 코드가 직관적임
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" }
- 하나의 테스트 메소드에는 하나만 검증한다.
- 버그 방지, 빠른 피드백 향상
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 // 격리되지 않은 테스트 코드. 비추천 }
- 테스트 상호 의존성을 피해라
- 버그 방지, 빠른 피드백 향상
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" }
- 테스트 코드는 테스트 하려는 기능의 사용 설명서여야 한다.
- 테스트 코드는 제품 코드 작성 라이프 사이클에 포함된다.
- 망각 곡선
- 사람은 20분이지나면 42%를 까먹는다.
Q&A
- 테스트 커버리지에 대해
- 없는 것 보다는 있는 게 좋다.
- 다만 과할 필요는 없다.
- 만약 의미있는 테스트 코드를 다 작성했는데도 70%밖에 되지않는다면 커버리지를 높이기 위해 불필요한 테스트 코드를 생성시킬 수 있다. 50% 정도만 되어도 충분할 것 같다.
- 고전파를 지향한다고 하셨는데 의존성 주입에 대해
- 메인 기능 위주로 작성하되, 서브 기능 같은 경우는 목킹을 해서 작성하기도 함
- 코드가 변경이 되면 테스트가 반드시 깨져야 한다 는 생각이 있는 팀이라면
- 팀이 공감할 수 있게 스터디를 한다거나 공유 세션이 필요함. 함께 호흡하는 것이므로 팀과 함께 맞춰가는 게 더 중요함
참고링크
Comment
TDD를 실천하고 있는 회사에 다니고 있지는 않지만 요즘 테스트 코드에 대해 숙련도를 높이기 위해 해당 세션을 선택했다.
최근 이펙티브 자바, 이펙티브 코틀린, DDD 북 스터디를 하면서 책을 자주 읽고 있는데 금일 발표에서도 Unit Testing 이란 책을 기반으로 발표해주셨다. Unit Testing 책을 읽지 않았음에도 간결하고 시원한 발표에 책을 읽은 듯한 느낌이 들어 만족스러웠다. 최고 👍🏻
Good Practice VS Bad Practice
의 3번 항목은 실무에서 테스트 코드 짤 때 고민을 해봤던 요소이기도 하고, Q&A 에서 테스트 커버리지에 대해 높아야만 하는 지에 대한 고민도 있었는데 둘 다 답변을 받을 수 있어서 좋았다.앞으론 단순히 테스트 코드를 짜기보다 어떻게 해야 더 좋은 테스트 코드가 될 수 있을 지 생각해보며 짤 수 있을 것 같다!