테스트하기 쉬운 코드로 개발하기 - 정진욱님
테스트하기 쉬운 코드란?
- 같은 입력에 항상 같은 결과를 반환하는 코드 -
Deterministic
- 항상 결과가 같지 않는 코드란?
- 시간에 의해 결과가 결정되는 함수.
- 오늘 날짜를 구하는 함수
- DB의 의해 결과가 결정되는 함수.
- 전체 회원의 수를 구하는 함수
- 시간에 의해 결과가 결정되는 함수.
- 항상 결과가 같지 않는 코드란?
- 외부 상태를 변경하지 않는 코드 -
No side effect
- 외부 상태를 변경하는 함수란?
Console.WriteLine(result);
- 외부 상태를 변경하는 함수란?
테스트 시나리오는 어떻게 작성할까?
- 유효성 검사
- Email : @가 포함되었는가?
- 이름 : 숫자가 포함되었는가?
- ConferenceId : 음수인가?
- 이미 등록된 좌석 수 DB에서 읽어오기
- 요청한 좌석 수가 확보 가능한지 판단하기
- 잔여 좌석수(전체 좌석 수 - 등록된 좌석 수) >= 요청한 좌석수
- 한꺼번에 10좌석을 초과해서 등록할 수 없다.
- 등록 정보 저장
- HTTP 결과 반환
위 테스트 시나리오 중 테스트하기 어려운 부분 어느 곳일까?
이미 등록된 좌석 수 DB에서 읽어오기
- DB는 항상 같은 입력(Query)에 항상 같은 결과를 반환하지 않는다.
등록 정보 저장
외부 상태에 의존적인 코드.
어떻게 이 문제를 해결할까?
- 테스트하기 쉬운 코드와 어려운 코드를 분리하자.
- 외부 상태에 의존적인 부분이 어디인지 확인하고 분리하자.
- DB를 조회하는 부분
- 테스트 데이터를 넣고 테스트.
- 외부 상태에 의존적인 부분이 어디인지 확인하고 분리하자.
- 외부 상태에 의존 적이지 않는 코드
- 즉 테스트를 하기 쉬운 코드는 도메인 객체에
테스트하기 쉬운 코드와 테스트하기 어려운 코드가 만나는 부분을 최대한 end-point(controller) 쪽으로 작성하여 결합도를 낮춘다.
TDD 맛보기
이메일에_골뱅이가_없으면_유효하지_않은_형식입니다(){
//Arrange
var sut = new ConferenceRegistration{Email = "nesoy.gmail.com"};
//Act
string actual = sut.validate();
//Assert
Assert.Notnull(actual);
}
TDD로 code를 작성할 때 주의할 점.
- 테스트를 만족하는 만큼만 코드를 작성하자.
- 그 이상 코드를 작성하게 되면 테스트로 커버할 수 없다.
- 즉 코드의 변경되는 부분을 놓칠 수 있다.
작게 작게
변경되는 부분에 집중하고 Cover하자.
두 부류 코드(테스트 하기 쉬운 코드, 테스트 하기 어려운 코드)가 만나는 Edge에서 어떻게 테스트하는가?
- 수동 테스트
- curl, Postman
- 자동 테스트
- DB에 테스트 데이터를 저장하고
- POST로 API를 호출합니다.
- 결과 값을 검증하는 과정을 진행합니다.
Mock 사용 한다는 건?
- 작성된 코드사용을 강제할 수 있다.
- Mock을 통해 이음새(Seam) 도입
- 행위 검증(mock)
- 어떤 메소드가 호출되었는가?
- 추상화가 필요함. ->
불필요한 추상화가 필요
- Outside-in
- 상태 검증(value)
- 결과 값이 무엇인가?
- 불필요한 추상화 필요없음.
- 구현된 코드에만 의존하기 때문에 실제로 데이터가 필요.
- Inside-out
Mock의 단점은 무엇일까?
- Mock이 사용하기 쉬우니 남발할 가능성이 크다.
- 대부분 Mock 사용 예제는 간단하다. 그래서 장점이 크게 보인다.
- 실제 프로젝트에 적용하면 한꺼번에 많은 수의 Mock을 다루면서 곤란을 겪는다.
- 적당 수의 Mock 사용에 대한 답을 찾기 어렵다.
- 때로는 상태 검증으로 돌아가보자.
Mock을 대신하여 상태 검증를 한다는 것은?
- TDD를 통한 사전이 아니라 사후 테스트를 하자.
- 난해한 코드가 아니다.
- 구현된 코드를 사용하지 않고 굳이 어려운 길을 택할 이유가 없다.
- 완벽을 추구하면서 Mock을 사용하는 비용을 들일 필요가 있는가?
정리하자면?
- 테스트하기 쉬운 코드란?
- 항상 같은 결과 반환
- 외부 상태를 변경하지 않는 코드
- 테스트하기 쉬운 코드로 개발하기
- 테스트하기 쉬운 코드와 어려운 코드 분리
- 두 부류의 코드는 최대한 가장 자리에 위치(예외: 로깅, 퍼사드)
- 테스트하기 어려운 코드를 최대한 가장자리에서 만나게 만들자.
- endpoint
Reference
후기
- 과도한 Mock은 정답이 아닌듯 하다.
- Mock과 상태 검증 적절히 사용하는 것이 중요한 Point라고 생각된다.
의식적인 연습으로 TDD, 리팩토링 연습하기 - 박재성님
- TDD < Refactoring
의식적인 연습이란?
- 무조건 연습을 많이 한다고 잘할 수 있을까?
- 테스트하기 쉬운 코드와 테스트하기 어려운 코드를 보는 눈
- 테스트하기 어려운 코드를 테스트하기 쉬운 코드로 설계하는 감(sense)
- 목적 의식 있는 연습에 얼마나 많은 시간을 투자했느냐?
- 1만 시간의 재발견
- 구체적인 목표 세우고 실행하기
- 피드백과 피드백에 따른 행동 변경을 수반
의식적인 연습으로 TDD, 리팩토링 연습 과정
- 단위 테스트 연습
- 사용하는 API 사용법을 익히기 위한
학습 테스트
작성 시작 - xUnit의 사용법을 익힐 수 있다.
- 사용하는 API에 대한 학습 효과가 있다.
- input과 output이 명확한 클래스 메소드(보통 util 성격의 메소드)
- 사용하는 API 사용법을 익히기 위한
- TDD 연습
- 토이 프로젝트로 시작하기
- 의존관계가 없는 프로젝트로 시작하기
- Refactoring 연습
- Method 분리하기
- Depth가 2단계 이상
- Else를 안 쓰는 연습
- Method는 항상 SRP를 지켜야 한다.
한 번에 한 가지 명확하고 구체적인 목표를 가지고 연습하라. 연습은 극단적인 Rule을 세워 연습하는 것도 좋다. 그래야 insight도 생긴다.
극단적인 Rule이라면?
- 일급 Collection만 사용하기.
- 3개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다.
- 점진적으로 요구사항이 복잡한 프로그램을 구현한다.
의존관계 추가를 통한 난이도 높이기
- 앞 단계 연습을 잘 소화했다면 테스트하기 쉬운 코드와 어려운 코드를 분리하여 연습하기
- ATDD 기반으로 응용 애플리케이션 개발하기
- 컴파일 에러를 최소화하면서 Refactoring
Book
- 1만 시간의 재발견
- 소트웍스 앤솔러지 - 마틴 파울러
- Clean Code
Question
- comfort zone을 나오는 노하우는 있으신지?
- 시간과 마음적인 여유 확보하기.
- 확보한 시간과 여유로 의식적인 연습하기.
- 요구 사항이 변경되는 경우 테스트 변경 또한 많은 경우에는 어떻게 해야할까?
- 제대로 설계가 안된 경우.
- 설계에 다시 고민하고 생각하기.
- 변경이 한 곳으로 집중되어야 한다.
- 제대로 설계가 안된 경우.
후기
- 가장 재밋게 듣고 이해와 공감이 많이 가는 강의였다.
- 시간이 괜찮다면 Toy Project로 시작해봐야겠다.
- 우선 시간과 여유 확보하기.
코드 품질을 위한 테스트 주도 개발 - 한성곤님
TDD란?
- Fail Test
- Write Production Code
- Refactoring
From Test
- 동작하는 코드에 대한 자신감
- 회귀테스트를 통한 자유로운 리팩토링
- 코드에 대한 지식이 증가
- 개발 생산성 향상
From Test-first
- 과도한 설계를 피하고, 간결한 interface를 가짐
- 불필요한 기능(Gold-Plating)을 줄임
- 실행 가능한 문서(Executable documents)를 가짐
- 코드 품질을 높임
TDD Metric
- Software Quality
- External Quality + Internal Qulity
- Internal Qulity
- Coding Rules : 개발 표준 및 기본적인 관례 준수
- Potential Bugs : Inspection을 활용한 점검
- Comments : 적정한 주석, Public Method만 확인
- Duplication : DRY
- Complexity : 적절한 분포
- Unit Tests : Lack of Unit Test
후기
- 생각보다 측정하는 방식이 다양하고 측정하는 Metric 또한 너무 많았다.
- 단순히 Coverage가 높다고 좋은 Software는 아니라고 말씀해주신게 기억이 남는다.
테알못 신입은 어떻게 테스트를 시작했을까? - 이혜승님
- 이미 테스트 하기 쉽게 만들어져 있는 코드로 시작한다.
- 순수 함수
- 외부 의존성(Dependency)이 없는 함수.
- utility, helper
- 테스트 하기 어렵게 만들어져 있는 코드에서 테스트 하기 쉬운 것만 분리하기
- 중요도가 높은 비즈니스 로직이 포함된 부분
- 버그가 발견된 부분(과거 X)
- 결합이 낮고 논리는 복잡한 부분
- 외부 의존성을 제거하기 위해 Parameter로 작성하기.
- 중요도가 높은 비즈니스 로직부터 시작하기.
TDD의 좋은 점은?
- 불안감이 제거된다.
- 스펙 문서 가능
- 실제로 파라미터 뭐가 있는 지, 반환 값은 어떤 모양인지 등 스펙을 확인할 때 테스트 코드를 봅니다.
- 디자인 개선 효과
- 테스트 코드를 통해서 디자인 상의 결점을 꽤 자주 발견
- 학습 동기부여
- 개발 생산성 향상
- 버그를 추적하는 시간이 현저하게 줄어들어요.
- 테스트 안 해서 아낀 시간 <= 테스트 안 해서 나온 버그 고치는 시간
- 프로젝트 생산성 향상
- 비즈니스 로직의 허점을 사전에 발견하기도 해요
- 집중력 향상
- 동시에 한 가지 이상의 일을 하지 않도록 통제해준다.
TDD를 진행하면서 실수는?
- 테스트 대상 오류
- 비즈니스와 관련된 버그를 낼 가능성이 낮거나 없고,
- 테스트를 유지함으로써 얻는 이익 < 테스트 유지와 관리에 드는 비용일 때
- 테스트가 단언하고 있는 내용이 사용자에게 중요한 가치를 주는 것이 아닐때는 작성하지 않는다.
- 검증력이 떨어지는 테스트
- 잘못된 검증.
- 차라리 안하는게 낫다.
- 테스트를 앞서가는 프로덕션 코드
- 앞서나가는 코드를 작성하게 되면 빈틈이 생기게 된다.
- 빈틈을 통해 버그가 발생할 수 있는 가능성.
- 함수가 곳곳에 퍼지게 된다.
- 낮은 결합도는 얻었지만 높은 응집력은 얻지 못했다.
무엇을 검증할 것인가?
- 비즈니스 가치를 주는 부분을 테스트 하자.
- 테스트 코드도 코드. 개선하고 유지보수한다.
후기
- 비슷한 경험도 했고 테스트를 작성하기에 앞서 비즈니스 가치를 주는 지 확인하는 작업도 필요해 보인다.
테스트를 돌보기 위한 간단한 실천 방법, 효과 - 양완수님
- 귀찮은 것.
- 고통스럽게 하는 것.
- 숨겨진 본질
- 테스트 욕심쟁이
- 테스트가 실패하는 이유는 단 하나
- 하나의 테스트는 오직 한 가지만 똑바로 검사해야 한다.
- 무엇을 테스트하는지? 인지능력의 과부화
- 흩어진 코드와 데이터
- 매직넘버
- 테스트가 깨지기 쉬운 것들
- 높은 결합
- 낮은 응집
- 추상이란?
- 문맥(Context) 위에서 오직 관심 있는 것들에 대해서만 집중하여 명확하게 하는 것.
- 숨겨진 본질
- 낮은 추상화
- 들쭉날쭉한 추상화
- 끊어진 논리
- 알 수 없는 의도
Reference
후기
- 테스트 코드도 프로덕션 코드처럼 개선해나가는 모습이 인상적이였다.
당신의 TDD가 항상 실패하는 이유 - 이규원님
우리가 제어할 수 없는 것
- 외부 세상
- 현실 세계
- 인프라
- 외부 서비스
- 레거시
설계
- 낮은 결합
- 높은 응집
- 도메인 모델 보호
- 정보 숨김(information hiding)
- 어려운 설계 결정과 변경될 가능성이 높은 설계 결정들을 다른 모듈로부터 숨기는 것.
- 인터페이스 테스트
- 반복 주기
- 계획
- 실행
- 평가
- 공유하는 문화
- 목표
- 지식
- 도메인 모델과 플랫폼
- 도메인 모델은 플랫폼에 독립적이어야 한다.
단위 테스트는 통과되지만 항상 안전할까?
- 항상 안전하지 않기에 통합 테스트도 필요하다.
- 메뉴얼 테스트
- 기능 테스트
- 외부 연동 테스트인 경우?
- Fake Service를 만들어서 진행
후기
- 단위 테스트로는 발견할 수 없는 부분도 있는 실제 사례를 직접보게 되어 기쁘다.
- 사내에서도 통합 테스트의 필요성에 대해 조언을 많이 해주셨다.
Q & A
왜 TDD가 필요한가?
- 나를 위해서 하자.
- 전파하는 건 나중에 하자.
- 스스로 필요성을 느끼게 하는것이 가장 중요.
- TDD 좋은 점은?
- 삶의 만족도를 높여준다.
- 빠른 피드백을 통해 직무의 탈진을 낮춰준다.
- TDD는 어디서 생겼을까?
- Pair Programming을 하면서 생겼다.
- 먼저 애자일이란 환경이 갖춰줘야 한다.
- 애자일은 사회적인 행위.
- 나만 하는 것이 아니라 다같이 하는 행위
- 시간이 많이 필요합니다.
- 작은 변화를 일으키기 위해서는?
- 많은 도전과 실패하기
- TDD를 도입하려면?
- 한번에 도입하려 하지 말고 작은 Step 진행하자.
- 한번의 하나의 실패
- 테스트의 순서가 중요.
첫 테스트를 작성할때 핵심을 테스트하자. 첫 단추부터 잘못 작성하게 되면 방향성 잃게 되어 테스트의 작성 방향이 완전 다르게 진행된다.
커버리지는 의미 있는 숫자인가?
- Metric에 초점을 맞추게 되면 잃어버리는게 많다.
- 테스트의 품질도 중요하다.
- 지표를 높이기 위한 테스트는 좋지 않다.
- 테스트 커버리지에 초점을 맞추지 말자.