체크리스트

  • TDD 정의
  • TDD의 목표
  • 개발에 있어 TDD의 위치
  • TDD 진행 방식
  • TDD의 장점

기존에 있던 개발 방식

  • 기존에 있던 개발 방식
    • 문제 발생 또는 요구사항 발생 -> 기능 구현 -> 콘솔에 값 찍어보기 -> 간단한 테스트 - >에러 발생 여부에 따라 기능 구현부터 다시 실행 -> 완료!
    • 문제점
      • 특정 모듈의 개발 기간이 길어질수록 개발자의 목표의식이 흐려진다.
      • 작업 분량이 늘어날수록 확인이 어려워진다.
      • 개발자의 집중력이 필요해진다.
      • 논리적인 오류를 찾기가 어렵다.
      • 코드의 사용방법, 변경 이력을 개발자의 기억력에 의존하게 되는 경우가 많아진다.
      • 테스트 케이스가 적혀있는 엑셀파일을 보며 매번 테스트를 실행하는 게 점점 귀찮아져서 간소화하는 항목이 늘어난다.
      • 코드 수정 시에 기존 코드의 정상 동작에 대한 보장이 어렵다.
      • 테스트를 해보려면 소스코드에 변경, 번거로운 선행작업이 필요하다.
      • 노동력이많이 들어간다.

TDD

  • 프로그램을 작성하기 전에 테스트를 먼저 작성하는 것
    • 문서로 만들어 머리로 생각하고 눈으로 확인할 것인가?
    • 아니면, 에상 결과를 코드로 표현해놓고 해당 코드가 자동으로 판단하게 할 것인가의 차이
  • TDD의 목표
    • 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);
              }
          }      
          
    • 두번째 질문: 잔고 조회
      • getBalance 기능 작성을 위한 테스트 게이스를 작성한다.
        • 잔고 조회
          • 10000원 으로 계좌 생성
          • 잔고 조회 결과 일치
      • 테스트 수행 결과가 오류로 표시된 항목은 실패 항목으로 만든다.
        @Test
        public void testGetBalance() throws Exception {
          Account account = new Account(10000);
          if(10000 != account.getBalance()) 
              fail();
        }
        
    • 두번째 응답: 잔고 조회 기능 구현
      • 앞서 작성된 테스트 케이스를 이용해 잔고 조회 기능을 구현
        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실천법과 도구