체크리스트
- TDD 정의
- TDD의 목표
- 개발에 있어 TDD의 위치
- TDD 진행 방식
- TDD의 장점
기존에 있던 개발 방식
- 기존에 있던 개발 방식
- 문제 발생 또는 요구사항 발생 -> 기능 구현 -> 콘솔에 값 찍어보기 -> 간단한 테스트 - >에러 발생 여부에 따라 기능 구현부터 다시 실행 -> 완료!
- 문제점
- 특정 모듈의 개발 기간이 길어질수록 개발자의 목표의식이 흐려진다.
- 작업 분량이 늘어날수록 확인이 어려워진다.
- 개발자의 집중력이 필요해진다.
- 논리적인 오류를 찾기가 어렵다.
- 코드의 사용방법, 변경 이력을 개발자의 기억력에 의존하게 되는 경우가 많아진다.
- 테스트 케이스가 적혀있는 엑셀파일을 보며 매번 테스트를 실행하는 게 점점 귀찮아져서 간소화하는 항목이 늘어난다.
- 코드 수정 시에 기존 코드의 정상 동작에 대한 보장이 어렵다.
- 테스트를 해보려면 소스코드에 변경, 번거로운 선행작업이 필요하다.
- 노동력이많이 들어간다.
TDD
- 프로그램을 작성하기 전에 테스트를 먼저 작성하는 것
- 문서로 만들어 머리로 생각하고 눈으로 확인할 것인가?
- 아니면, 에상 결과를 코드로 표현해놓고 해당 코드가 자동으로 판단하게 할 것인가의 차이
- TDD의 목표
- Clean code that workd
- 잘 동작하는 깔끔한 코드
- Clean code that workd
- TDD 기원
- Agile 방식 중 하나인 XP의 실천방식 중 하나
- TDD는 XP에서 등장하는 여러 가지 실천 방법 중 하나로 테스트 우선 개발과 동일한 의미를 갖는다.
- 개발에 있어 TDD의 위치
- TDD는 개발자가 자신을 위해 처음으로 수행하는 테스트에 해당한다.
- 진행 방식
- 질문(Ask): 테스트 작성을 통해 시스템에 질문한다. (테스트 수행 결과는 실패)
- 응답(Respond): 테스트를 통과하는 코드를 작성해서 질문에 대답한다.(테스트 성공)
- 정제(Refine): 아이디어를 통합하고, 불필요한 것은 제거하고, 모호한 것은 명확히 해서 대답을 정제한다.(리팩토링)
- 반복(Repeat): 다음 질문을 통해 대화를 계속 진행한다.
예제
- 요구사항
- 은행계좌 클래스
- 계좌 잔고 조회
- 입금/출금
- 예상 복리 이자(추가 개발)
- (질문)계좌 생성 테스트
- 구현해야 하는 기능과 유의사항
- Account Class
- 기능
- 잔고 조회
- 입금
- 출금
- 유의사항
- 금액은 원 단위로(ex 천원 = 1000)
- AccountTest.java
public class AccountTest { public void testAccount() throws Exception { Account account = new Account(); if(account == null) throw new Exception("계좌 생성 실패"); } }
- 아직 Account 클래스를 생성하지 않았기 때문에 컴파일 오류 발생
- 구현해야 하는 기능과 유의사항
- (응답) 계좌 생성 메소드 구현
- 계좌 생성 테스트 케이스를 통과하는 코드를 작성한다.
- Account.java
public class Account { }
- 성공하는 지 확인한다.
- (정제)
- 리팩토링을 적용할 부분이 있는지 찾아본다.
- 소스의 가독성이 적절한가
- 중복된 코드는 없는가
- 이름이 잘못 부여된 메소드나 변수명은 없는가
- 구조의 개선이 필요한 부분이 없는가
- ToDo 목록에서 완료된 부분을 지운다.
- 첫번째 정제
- jUnit 적용 결정
public class AccountTest { @Test public void testAccount() throws Exception { Account account = new Account(); assertNotNull(account); } }
- jUnit 적용 결정
- 리팩토링을 적용할 부분이 있는지 찾아본다.
- 두번째 질문: 잔고 조회
- getBalance 기능 작성을 위한 테스트 게이스를 작성한다.
- 잔고 조회
- 10000원 으로 계좌 생성
- 잔고 조회 결과 일치
- 잔고 조회
- 테스트 수행 결과가 오류로 표시된 항목은 실패 항목으로 만든다.
@Test public void testGetBalance() throws Exception { Account account = new Account(10000); if(10000 != account.getBalance()) fail(); }
- getBalance 기능 작성을 위한 테스트 게이스를 작성한다.
- 두번째 응답: 잔고 조회 기능 구현
- 앞서 작성된 테스트 케이스를 이용해 잔고 조회 기능을 구현
public int getBalance() { return 10000; }
- 테스트 실패 시에 메시지를 보여줄 수 있는 구조를 생각한다.
- 주의점!
- 테스트 케이스를 엉성하게 만들면 테스트 자체를 신뢰할 수 없게 된다.
- 테스트 케이스를 통함 제품 코드 구현을 하드코딩으로 시작하는 것도 괜찮은 출발점이다.
- 테스트 코드 변경
@Test public void testGetBalance() throws Exception { Account account = new Account(10000); if(10000 != account.getBalance()) fail(); account = new Account(1000); if(1000 != account.getBalance()) fail(); Account account = new Account(50000); if(50000 != account.getBalance()) fail(); }
- 테스트 코드에 맞게 구현
public class Account { private int balance; public Account() {} public Account(int balance) { this.balance = balance; } public int getBalance() { return this.balance; } }
- 앞서 작성된 테스트 케이스를 이용해 잔고 조회 기능을 구현
- 두 번째 정제
- 구현된 잔고 조회 로직에 대한 리팩토링 작업
- 본격적으로 jUnit테스트 프레임워크 사용
@Test public void testGetBalance() throws Exception { Account account = new Account(10000); assertNotNull(account); assertEquals(10000, account.getBalance()); account = new Account(1000); assertEquals(1000, account.getBalance()); Account account = new Account(50000); assertEquals(50000, account.getBalance()); }
- 남은 기능도 이와 같이 질문 - 응답 - 정제 식으로 구현한다.
- 은행계좌 클래스
- TDD의 장점
- 품질 높은 소프트웨어 모듈 보유
- 자동화된 단위 테스트 케이스를 갖는다.
- 사용설명서 & 의사소통의 수단이 된다.
- 설계 개선
- 보다 자주 성공한다
** 참고 서적: 테스트주도개발 TDD실천법과 도구