TDD Pattern (1)

3 minute read

TDD-Pattern 1


일반적인 애플리케이션

TDD가 가장 적극적으로 사용되고 효율이 높은 부분은 애플리케이션의 업무로직을 구현할 때다. 그렇다면 ‘로직 구현’이 개발의 중심이 되는 일반적인 개발에서는 테스트 케이스를 작성할 때 어떠한 상황에 접하게 되는지 살펴볼 필요가 있다.

그리고 각각의 경우 어떻게 테스트 케이스를 작성하고, 또 어느 수준까지 진행할 것인지 미리 알아두면 시간절약에 많은 도움이 된다.


생성자 테스트

단순히 클래스를 생성한다는 의미를 갖는 생성자(constructor)는 굳이 테스트 케이스를 작성하지 않는다. 다만, 객체 사용을 위해 반드시 갖춰야 하는 값을 생성자에 설정하는 경우는 필요에 따라 테스트를 작성한다.

단, 가끔 생성자에서 객체 생성의 의미뿐 아니라, 선행조건이나 업무로직을 직접 기술하는 경우도 있는데, 이럴 경우에는 테스트 케이스를 작성해야 한다. 이를테면 DAO 객체 생성 시 DB 커넥션까지 확보해야 하는 경우라면 다음과 같이 테스트케이스를 만들 것이다.

public class EmployeeDaoTest {
@Test
public EmployeeDaoTest {
EmployeeDao dao = new EmployeeDao();
assertTrue(dao.isConnected());
}

** 일반적으로 좋은 애플리케이션이라면, 생성자의 파라미터로 지정된 값 외의 항목을 이용하는 코드를 생성자 메소드 안에 넣지 않는다. 자칫, 외견상 파악할 수 없는 숨겨진 로직이 되어서 모듈 자체의 복잡도를 높이기 때문이다. **

DTO 스타일의 객체 테스트

클래스가 속성 필드와 단순 setter/getter로만 이뤄진 DTO1 스타일로 만들어진 경우에는 굳이 테스트 케이스를 작성하지 않는다. 단, 특정한 목적을 갖고 만들어진 불변 객체(immutable object)의 경우에는 getter계열 메소드나 상태를 확인할 수 있는 is 계열(isReady, isConnected 등)의 메소드를 이용한 테스트 케이스를 작성하기도 한다.

불변 객체

한번 만들어진 다음엔, 내부 상태 값을 바꿀 수 없는 객체를 ‘불변 객체’라고 부른다. 단순하게 생각하면 setter가 존재하지 않는 클래스라고 보면 된다. 대표적으로 자바의 String 클래스가 이런 불변 객체에 해당한다. String은 한번 만들어진 다음에는 내부 값을 수정할 수 없다. 새로 생성해서 새롭게 변수에 할당하는 수밖에 없다.

String name = “성윤”;

name = “김” + name;

자바에서는 문자열을 위와 같이 사용할 수 있다. 마치 name의 앞에 “김”이 붙는 것처럼 보이지만, 실제로 문장은 아래와 같이 처리된다.

String name = new String(“성윤”);

name = new String(“김” + name);


닭과 달걀 메소드 테스트

메소드가 서로 맞물려 있어서, 완전히 하나만 독립적으로 테스트하기 어려운 경우가 있다. 보통 로직 메소드(add, remove, set 계열 메소드)와 상태확인 메소드(get, show, is 계열 메소드)가 짝을 이루는 경우에 해당한다. 이럴 경우엔 어느 한쪽의 테스트 케이스를 먼저 만들기가 어렵다. 이를테면 참가자(Attendee) 목록을 관리하는 클래스가 있다고 가정해보자. 테스트 케이스로 작성해야 하는 메소드는 다음과 같다.

Attendee 클래스

void add(String name) 참가자 목록에 이름을 추가한다.
String get(int order) 해당 번째(order)로 등록된 참가자의 이름을 보여준다.
public class AttendeeTest {
@Test
public void testAdd() throws Exception { // ➊
Attendee attendee = new Attendee();
attendee.add("백기선"); // ➋
assertEquals ("백기선", attendee.get(1)); // ➌
}
@Test
public void testGet() throws Exception { // ➍
Attendee attendee = new Attendee();
attendee.add("백기선"); // ➎
assertEquals ("백기선", attendee.get(1)); // ➏
}
}

➊ 참석자를 추가하는 add 메소드에 대한 테스트 케이스 작성을 시작한다.

➋ ‘백기선’이라는 참가자를 추가한다.

➌ 시나리오의 흐름상 참석자가 정상적으로 추가됐는지 확인하기 위해 get 메소드를 호출한다. 그런데 아직 get 메소드는 만들어지지 않은 상태다. get 메소드가 선행 구현되어 있어야 add 메소드 구현을 위한 테스트 케이스 작성이 가능하다는 사실을 알게 된다.

➍ get 기능을 구현하기 위해 testGet 테스트 메소드 작성을 시작한다.

➎ 테스트 케이스 시나리오상, get 메소드의 기능을 테스트하려면 참가자 추가기능에 해당하는 add 메소드 작성이 선행되어 있어야 한다. 아.. 뭔가 조금 꼬이는 느낌이다. add를 구현할 수 없어서 만들다 말고 get 먼저 구현하려고 넘어온 건데???

➏ add가 선행 구현되어 있지 않으면 asssertEquals 문장이 의미가 없어진다.

이러한 상황에서 선택할 수 있는 방법은 세 가지 이다.

  • 실패하는 테스트 케이스가 두 개인 상태에서 작업하기(일반적인 방법)
  • 안정성이 검증된 제3의 모듈을 사용하기(가능만 하다면, 권장)
  • 자바 리플렉션을 이용해 강제로 확인하기(대체적으로 비권장)

그 외 상황들

  • 배열 테스트

  • 기본적으론 순서까지 따지기 때문에, 만일 순서는 고려하지 않고 비교 할 때는 Arrays.sort를 사용하거나 Unitils의 지원 메소드를 사용하거나, JUnit3의 경우 List로 변환해서 비교한다.

  • 객체 동치성 테스트

  • 객체와 객체를 비교할 때는 정말 동일한 객체인지 판별해내야 하는 동일성 테스트인지, 같은 값을 갖는 객체인지만 판별하면 되는 동치성 테스트인지를 구분해야 한다.

이를 해결하기 위해선

  • 내부 상태(보통은 필드값)를 직접 꺼내와서 각각 비교한다.
  • ToString을 중첩구현(Override) 하고 toString 값으로 비교한다.
  • equals 메소드를 중첩구현(Override) 한다.
  • Unitils의 assertReflectionEquals를 이용한다.

Categories:

Updated: