www.youtube.com/watch?v=Cz_a2gQp63c
연사 소개 : 정진욱 PUBLYTO CPO
테스트하기 쉬운 코드란?
- 같은 입력에 항상 같은 결과를 반환하는 코드
= 결정적인
= Deterministic
public int Add(int x, int y) {
return x + y;
}
- 외부 상태를 변경하지 않는 코드
= 부수효과가 없는 코드
= No side effects
public int Add(int x, int y) {
return x + y;
}
테스트하기 어려운 코드
같은 입력에 항상 같은 결과를 반환하지 않는 코드 1
public string GetAMOrPM() {
var now = DateTime.Now;
if (now.Hour < 12) {
return "AM";
} else {
return "PM";
}
}
외부 상태를 변경하는 코드
public int Add(int x, int y) {
int result = x + y;
Console.WriteLine(result);
return result;
}
들어가기에 앞서
- 예제 코드는 C#으로 작성되어 있습니다.
- 혹시 코드를 놓치신다면 세세한 곳에 얽매이지 마시고, 전체 스토리를 봐주세요.
컨퍼런스 등록 Web API Endpoint
public class ConferenceRegistrationsController : ControllerBase {
public ActionResult Post(ConferenceRegistration registration) {
throw new NotImplementedException();
}
}
1. ConferenceRegistration 유효성 검사
public class ConferenceRegistration {
public string Email { get; set; }
public string Name { get; set; }
public int ConferenceId { get; set; }
public int Seats { get; set; }
}
2. 이미 등록된 좌석 수 DB에서 읽어오기
3. 요청한 좌석 수가 확보 가능한지 판단
- 전체 좌석 수 – 등록된 좌석 수 >= 요청한 좌석수
- 한꺼번에10 좌석을 초과해서 등록할 수 없다
4. 등록 정보 저장
5. HTTP 결과 반환
테스트하기 어려운 단계는?
2. 이미 등록된 좌석 수 DB에서 읽어오기
4. 등록 정보 저장
2, 3 단계를 함께 구현해보자
2. 이미 등록된 좌석 수 DB에서 읽어오기
3. 요청한 좌석 수가 확보 가능한지 판단
- 전체 좌석 수 – 등록된 좌석 수 >= 요청한 좌석수
- 한꺼번에 10좌석을 초과해서 등록할 수 없다
using (var dbContext = new ConferenceDbContext()) {
IQueryable<int> query =
from registration in dbContext.ConferenceRegistrations
where registration.ConferenceId == conferenceId
select registration.Seats;
int registeredSeats = query.Sum();
}
sql로 실행되는 쿼리
select sum(Seats)
from ConferenceRegistrations
where ConferenceId = 13
한꺼번에 10좌석을 초과해서 등록할 수 없다
if (requestSeats > 10) {
return false;
}
전체 좌석 수 – 등록된 좌석 수 >= 요청한 좌석수
if (capacity - registeredSeats >= requestSeats) {
return true;
}
return false;
테스트하기 쉬운 코드와 어려운 코드를 구분한다면?
녹색 : 테스트하기 쉬움
빨간색 : 테스트하기 어려움 어떤 값을 가져올지 모른다.
결국 CanBeRegisterd메소드는 테스트하기 어려운 코드가 된다.
어떻게 해야 할까요?
분리합시다
빨간 부분의 분리
녹색 부분의 분리
테스트하기 쉬운 코드로 개발하기
- 방법 1: 테스트하기 쉬운 코드와 어려운 코드 분리
- 방법 2: 두 부류의 코드는 어디서 만나야 하나?
TDD 맛보기
원칙 : 테스트를 만족하는 만큼만 코드를 작성하라
방법 2 : 두 부류의 코드는 어디서 만나야 하나?
메소드를 호출하는 구조의 시각화(아래 이미지)
테스트하기 어려운메소드를 호출하는 메소드 역시 테스트하기 어렵다.
결국 가장 바깥쪽에 있는 초록색 사각형도 빨간색 사각형으로 바뀐다.
어떻게 해야 할까요?
분리해서, 최대한 가장자리에서 만나게 합니다.
정리
- 테스트하기 쉬운 코드란?
- TDD 맛보기
- 테스트하기 쉬운 코드로 개발하기
- 방법1: 테스트하기 쉬운 코드와 어려운 코드 분리
- 방법 2: 두 부류의 코드는 최대한 가장자리에 위치 (예외: 로깅, 퍼사드 )
- 방법 3: 두 부류 코드가 만나는 가장자리는 어떻게 테스트하나?
가장자리는 어떻게 테스트하는가?
- 수동 테스트
- 자동 테스트
문제는? : 구현해 놓은 클래스를 사용하지 않고, 누군가가 재작성해서 테스트할 수도 있다.
작성된 코드 사용을 강제할 수 있나?
현 상태의 코드(디자인)로는 할 수 없다.
실제 클래스 대신 목(Mock) 사용을 위해 이음새(Seam)가 있어야 한다.
이음새(Seam) 도입
public interface IConferenceRepository {
int QueryRegisteredSeats(int conferenceId);
void Save(ConferenceRegistration registration);
}
목(Mock) 사용
- 작성된 코드 사용을 강제할 수 있다.
- 목 사용이 큰 장점으로 보이지만, 생각해볼 점이 많다.
제가 생각하는 목(Mock) 사용 문제점
- 목을 남발할 가능성이 크다.
- 대부분 목 사용 예제는 간단하다. 그래서 장점이 크게 보인다.
- 실제 프로젝트에 적용하면 한꺼번에 많은 수의 목을 다루면서 곤란을 겪는다.
- 적당 수의 목 사용에 대한 답을 찾기 어렵다.
- 상태 검증으로 돌아가 보자.
상태 검증 - 문제 극복 방안
- TDD를 통한 사전이 아니라 사후 테스트를 하자.
- 두 부류의 코드가 맞물려 잘 돌아가는 로직이다.
- 난해한 코드가 아니다.
- 구현된 코드를 사용하지 않고 굳이 어려운 길을 택할 이유가 없다.
- 완벽을 추구하면서 목을 사용하는 비용을 들일 필요가 있는가
정리(최종)
- 테스트하기 쉬운 코드란?
- 테스트하기 쉬운 코드로 개발하기
- 방법 1: 테스트하기 쉬운 코드와 어려운 코드 분리
- 방법 2: 두 부류의 코드는 최대한 가장자리에 위치
- 방법 3: 가장자리를 테스트하는 방법을 익히자
- 수동
- 자동: 상태 검증 / 행위 검증
끝으로
- 요즘 저는
- 두 부류 코드를 분리해서 각각 테스트하고,
- 가장자리에서 맞물려 돌아가는 코드는 주로 수동 테스트합니다.
- 두 부류 코드 섞어 넣고 테스트가 어렵다고 포기하지 마세요.
- 위 내용과 관련된 제 블로그 글이 있답니다.
'세미나' 카테고리의 다른 글
SLASH 21 컨퍼런스 (0) | 2021.05.01 |
---|---|
[2월 우아한테크세미나] 우아한 스프링 부트 (0) | 2021.04.04 |
[자바 라이브 스터디] 종료 기념 리뷰 (0) | 2021.03.21 |
[OKKYCON 2021 Live] 협업의 기술 (0) | 2021.03.06 |
YOUTHCON'20 컨퍼런스 요약 (0) | 2020.12.20 |