관리 메뉴

너와 나의 스토리

TDD(Test-Driven Development) 테스트 주도 개발 패턴 정리 본문

개발/TDD(Test-Driven Development)

TDD(Test-Driven Development) 테스트 주도 개발 패턴 정리

노는게제일좋아! 2021. 3. 31. 17:00
반응형

1. 테스트 우선

  • 테스트부터 작성하고 구현하자!
    • 프로그램 설계나 작업 범위 조절에 유용하다.

 

2. 단언(Assert) 우선

  • 완료된 시스템이 어떨 거라고 정하고 시작.
  • 예: 소켓을 통해 다른 시스템과 통신하려 한다고 가정하자.
  • 요구 조건: 통신을 마친 후 소켓은 닫혀있어야 하고, 소켓에서 문자열 'abc'를 읽어와야 한다.
    • 이를 단언으로 먼저 작성한다.
assertTrue(reader.isClosed());
assertEquals("abc", reply.contents());
  • reply는 어디서 얻어오나?
    • socket에서 얻어온다. -> Socket reader;
Buffer reply = reader.contents();

assertTrue(reader.isClosed());
assertEquals("abc", reply.contents());
  • socket은 어디에서 나오나?
    • 서버에 접속할 때 생성된다.
Socket reader = Socket("localhost", defaultPort());
Buffer reply = reader.contents();

assertTrue(reader.isClosed());
assertEquals("abc", reply.contents());
  • 이 작업을 하기 위해 필요한 사전 작업은 무엇인가?
    • 서버를 먼저 열어야 한다.
    @Test
    public void testCompleteTransaction(){
        Server writer = Server(defaultPort(), "abc");
        Socket reader = Socket("localhost", defaultPort());
        Buffer reply = reader.contents();
        assertTrue(reader.isClosed());
        assertEquals("abc", reply.contents());
    }
  • 이런 식으로 테스트 코드를 작성해나가면 된다.
  • 참고: assertEquals()에는 허용 오차를 적어 넣을 수 있다.
@Test
public void testWidth(){
	Rectangle empty = new Rectangle(0,0,0,0);
	assertEquals(0.0, empty.getWidth(), 0.0);
}

 

3. 명백한 데이터를 사용하라

  • 예: 한 통화를 다른 통화로 환전하려고 한다. 이 거래에는 수수료 1.5%가 붙는다. USD에서 GBP로 교환하는 환율이 2:1이라면 $100를 환전하려면 50GBP-1.5% = 49.25GBP여야 한다.
  • 테스트 코드 1보다는 2처럼 명확한 데이터를 기입하고, 단언 부분에 수식으로 표기하는 것이 좋다.
    • 이렇게 하면 테스트에서 입력으로 사용된 숫자와 예상되는 결과 사이의 관계를 읽어낼 수가 있다. 
    • 또, 단언 부분에 수식으로 써놓으면 다음으로 할 일을 쉽게 알 수 있다.

<테스트 코드 1>

Bank bank = new Bank();
bank.addRate("USD", "GBP", STANDARD_RATE);
bank.commission(STANDARD_COMMISSION);
Money result = bank.convert(new Note(100, "USD"), "GBP");
assertEquals(new Note(49.25, "GBP"), result);

<테스트 코드 2>

Bank bank = new Bank();
bank.addRate("USD", "GBP", 2);
bank.commission(0.015);
Money result = bank.convert(new Note(100, "USD"), "GBP");
assertEquals(new Note(100/2*(1-0.015), "GBP"), result);

 

4. 아는 것부터 테스트하라

  • 하나의 연산을 추가할 때 고려할 점
    • 이 오퍼레이션을 어디에 두어야 하나?
    • 적절한 입력 값은 무엇인가?
    • 이 입력들이 주어졌을 때 적절한 출력은 무엇인가?
  • 아는 것부터(작은 것부터) 작성하기!
    • 일단 오퍼레이션이 아무 일도 하지 않는 경우부터 테스트하라
      • -> 어디에 둘 지부터 결정

 

5. 모의 객체를 사용하라

  • 실제 데이터베이스를 사용하지 말고 마치 데이터베이스인 것처럼 행동하지만 실제로는 메모리에만 존재하는 객체를 만들어 테스트 코드를 작성하라.
  • 데이터베이스는 시작 시간이 오래 걸리고, 깨끗한 상태로 유지하기 어렵다.

 

6. 프로그래밍 중간에 멈출 때, 마지막 테스트가 깨진 상태로 중단하라

  • 깨진 테스트가 책갈피 역할을 할 것이다.
  • 이 테스트를 보면 어느 작업부터 시작할지 명백히 알 수 있다.
  • But 팀 프로그래밍을 하는 중이라면 모든 테스트가 성공한 상태로 끝마치는 것이 좋다.

 

7. 가짜로 구현하기

  • 실패하는 테스트를 만든 후 첫 번째 구현은 어떻게 하는게 좋을까?
    • 상수를 반환하게 하라!
  • 일단 테스트가 통과하면 단계적으로 상수를 변수를 사용하는 수식으로 변형한다.

 

8. 픽스처

  • 여러 테스트에 걸쳐 동일하게 사용되는 객체들을 테스트 픽스처(fixture) 또는 발판(scaffolding)이라고 부른다.
  • 여러 테스트에서 공통으로 사용하는 객체들을 생성할 때 어떻게 하면 좋을까?
    • 각 테스트 코드에 있는 지역 변수를 인스턴스 변수로 바꾸라
    • setUp() 메서드를 재정의하여 이 메서드에서 인스턴스 변수들을 초기화하도록 한다.
  • 중복의 장점
    • 위에 말한 것처럼 중복된 객체들을 setUp()에서 정의하지 않고, 중복해서 사용하는 것에도 장점이 존재한다.
    • 객체 세팅 코드들이 Assert에 적힌 메서드에 포함되는 경우, 우리는 테스트 코드를 그냥 위에서 아래로 읽어내려갈 수 있다. 
      • 객체 세팅 코드들이 setUp()으로 분리한 경우, 우리는 해당 메서드(setUp()에 작성된)가 자동으로 호출된다는 점과 객체들이 어떻게 초기화되었는지를 기억해야 한다.
  • 다른 픽스처가 필요한 경우
    • 같은 객체 타입이지만 입력 값이 조금 다른 객체를 테스트하는 경우가 있을 수 있다.
    • 이 경우, 새로운 TestCase 하위 클래스를 만들어서 정의할 수 있다.
    • 즉, 다른 종류의 픽스처를 필요로 하는 테스트는 다른 클래스에 존재하게 된다.

 

9. 테스트 메서드

  • 테스트 메서드 네이밍
    • 'test'로 시작하자.
      • 예: testCompleteTransaction
  • 테스트를 작성하기 전에 짧은 아웃라인 적기
    • 예: 
      • /* tuple space에 추가하기 */
      • /* tuple space에서 갖고오기 */
      • /* tuple space에서 읽어들이기 */
      • /* 존재하지 않는 tuple 가져오기 */
    • 이러한 아웃라인은 특정 테스트를 추가할 때까지 임시 자리 표시(place holders)가 된다.

 

 

 

 

출처:

- [테스트 주도 개발 Test-Driven Development: By Example"]

반응형
Comments