Home 7장 개발 영역에 따른 TDD 작성 패턴
Post
Cancel

7장 개발 영역에 따른 TDD 작성 패턴

7.3 일반적인 애플리케이션

TDD가 가장 적극적으로 사용되고 효율이 높은 부분은 애플리케이션의 업무 로직을 구현할 때다.

생성자 테스트

단순 클래스를 생성하기 위한 생성자의 경우 테스트 케이스를 작성하지 않는다. 다만 객체 사용을 위한 필수 값을 설정하는 경우에는 필요에 따라 테스트케이스를 작성한다. 또 선행조건이나 업무로직을 직접 기술하는 경우도 있는데 이럴 경우는 테스트 케이스를 작성해야 한다.

DTO 스타일의 객체 테스트

단순 getter/setter로 이루어진 DTO는 굳이 테스트 케이스를 작성하지 않으나 특정 목적을 가진 불변 객체의 경우에는 테스트 케이스를 작성하기도 한다.

닭과 달걀 메소드 테스트

메소드가 서로 맞물린 경우 완전히 하나만 독립적으로 테스트하기 어려운 경우가 있다. 좋은 테스트 케이스 작성법 중 하나가 한 번에 실패 케이스를 하나씩 작성하는 것이나 이 상황에서는 어느 한쪽만 먼저 구현하기 어려운 상황이 된다.

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

테스트 케이스를 리플렉션을 통해 해결하는 것은 잘못된 접근 방식이다. 리플렉션은 정말 부득이한 경우가 아니면 사용하지 않는다. 예를 들면 private메소드를 부득이 테스트해야 하거나 소스코드 없는 외부 모듈을 받았는데 신뢰도 검증을 위해 해당 모듈의 내부 메소드를 테스트해야 하는 경우 등이 특별한 예외에 해당한다.

배열 테스트

  • JUnit 4의 assertArrayEquals를 이용한다 - 기본적으로 순서를 따지기 때문에 순서를 고려하지 않을 경우 Arrays.sort를 이용하자
  • Unitils의 assertReflectionEquals나 assertLenientEquals를 이용한다. -
  • JUnit 3의 경우라면 List로 변환해서 비교한다.

객체 동치성 테스트

객체 비교시 정말 동일한 객체인지를 판별해내야하는 ‘동일성 테스트’인지 아니면 같은 값을 같는 객체인지만 판별하면 되는 ‘동치성 테스트’ 인지 구분해서 생각해야 한다. 두 객체를 서로 동치비교하는 방법에는 다음과 같이 있다.

  • 내부 상태(필드값)를 직접 꺼내와서 각각 비교한다.
  • toString을 중첩구현해 놓고, toString 값으로 비교한다.
  • equals 메소드를 중첩 구현한다. (개념적으로 가장 올바른 방법이다.)
  • Unitils의 assertReflectionEquals를 이용한다.

컬렉션 테스트

  • 자바 기본형이나 String이 컬렉션에 들어가 있는 경우 - 곧바로 비교 가능
  • 일반 객체가 컬렉션에 들어 있는 경우 - 객체가 equals를 중첩구현 했다면 문제가 없지만, 안했다면 객체의 id값을 비교하기 때문에 값이 서로 다르다고 인식한다.

7.2 웹 애플리케이션

웹 애플리케이션의 기본 흐름은 HTTP 기반에서의 요청/응답이 전부다. 화면의 브라우저에서 무언가를 요청하면 서버는 적절한 응답을 한다. TDD로 개발하겠다라고 마음을 먹는다면 어떤 개발 영역이 있는지 살펴보고 영역별로 접근 전략을 잘 세워야 한다.

  • 개인 PC의 웹 브라우저
  • 서버
  • 웹 애플리케이션의 구조

MVC 아키텍처

MVC 아키텍처는 웹 애플리케이션을 모델-뷰-컨트롤러라는 세 개의 영역으로 나누어 구성하는 것을 지칭한다. MVC의 장점은 view와 model의 느슨한 결합, 관심의 분리, 테스트 주도 개발의 용이함, 각 영역의 재사용성이 높아짐이 있다.

  • 뷰 - 웹 애플리케이션의 사용자와 직접적으로 대화가 이뤄지는 부분, 사용자의 동작과 연관되어 있다. 비교적 자주 변경되는 부분이라 많은 시간이 소요된다. 따라서 뷰를 TDD로 개발하려면 쉽지 않다.
  • 컨트롤러 - 모델과 뷰를 분리하기 위해 사용되는 중간 층이다. 컨트롤러에 대한 TDD는 비교적 수월하다. 요청으로부터 적절한 데이터를 발췌해내고, 해당 데이터를 모델로 넘긴다. 그리고 그 결과로 모델로부터 받은 데이터를 응답에 담아서 뷰로 넘기는 구조가 가장 기본적이다.
  • 모델 - 모델은 오로지 무언가의 호출에 응답할 뿐이고, 컨트롤러가 모델을 호출하는 역할을 맡고 있다. 웹 애플리케이션 구성요소 중 가장 TDD가 쉽게 적용되는 부분이고, 또 적용해야하는 부분이다.

뷰 TDD

  1. HttpUnit : 가상 웹 브라우저를 이용해 웹 페이지의 요소들을 테스트하기 위해 사용하는 프레임워크이다. HttpUnit을 통한 TDD 웹 개발은 거의 불가능에 가깝다.
  2. Selenium :
    • Selenium IDE : 브라우저와 통합돼서 녹화/실행/확인 기능을 제공한다. 현재 파이어폭스 브라우저만 지원한다.
    • Selenium RC(Remote Control) : 브라우저를 원격으로 조종해서 웹 페이지에 대한 테스트를 수행한다.
    • Selenium Grid : 여러 개의 Selenium RC 서버를 구동해서 테스트 수행의 스케일업을 지원한다.

    위 중 TDD로 뷰를 작성하기 위해 사용할 수 있는 부분은 Selenium RC 이다. Selenium은 테스트 수행시에 웹 브라우저를 실제로 기동시킨다. 그 다음 웹 브라우저를 조종해서 테스트를 수행하기 때문에 실제와 같은 테스트가 가능하다.

  3. Cubic Test : 웹 애플리케이션의 기능을 테스트하는 부분에 기능이 집중되어 있다. Cubic Test는 GUI 기반에서 순서도를 작성하듯 웹 애플리케이션에 대한 기능 테스트를 작성할 수 있다. 내부적으로는 Selenium RC가 동작한다.

컨트롤러 TDD

컨트롤러를 테스트하는 가장 간단한 방법은 뷰로부터 넘어오는 요청을 가상으로 만들어주고, 그 결과에 해당하는 응답이 예상과 일치하는지 판단하는 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void testSearchByEmpid() {
  MockHttpServletRequst request = new MockHttpServletRequst();
  MockHttpServletResponse response = new MockHttpServletResponse();

  request.addParameter("empid", "5874");

  EmployeeSearchServlet searchSevlet = new EmployeeSearchServlet();
  searchSevlet.service(request, response);

  Employee employee = (Employee)request.getAttrubute("employee");

}

모델 TDD

  • 도메인 모델에 대한 TDD : 대표적인 예가 DTO이고, 대부분의 DTO는 단순하기 때문에 TDD로 작성할 필요가 거의 없다.
  • 서비스 모델에 대한 TDD : 기능 위주로 구성된 클래스들로 애플리케이션의 핵심적인 부분이다.
  • 스쳐 지나가는 서비스 모델 : SQL과 DAO를 연결해주는 것과 결과값을 객체로 받는것이 전부인 서비스 모델이 있다.

웹 애플리케이션에 대한 TDD 접근 전략 정리

  • 모델, 뷰, 컨트롤러를 최대한 분리시킨다.
  • 부는 단순히 표현 계층으로 보고 업무 로직이 들어가지 않도록 유지한다.
  • 모델에 대한 TDD는 최대한으로 적용한다.

7.3 데이터베이스

데이터베이스 관련 테스트는 몇 가지 어려움이 있는데 1)테스트 진행시 데이터베이스에 들어있는 데이터의 상태가 바뀐다. 2)테스트 전,후의 데이터 비교가 쉽지 않다.

데이터베이스 상태가 바뀌는 문제의 일반적인 해결방법

  1. 트랜잭션을 선언하고 테스트 케이스를 수행한 다음 롤백처리한다.
  2. 테스트 케이스 작성 시 ‘입력->수정->삭제’ 순서대로 테스트 케이스가 실행되도록 만든다.
  3. SQL 스크립트가 테스트 수행시에 실행되도록 만든다.
  4. DbUnit을 사용한다.

테스트 전후의 데이터베이스 상태를 비교하는 방법

  1. 예상 결과를 미리 다른 테이블에 넣어놓고, 대상 테이블과 예상 테이블을 각각 Select문으로 결과를 받아와서 바교한다.
  2. 예상 결과를 미리 문자열로 만들어놓고 Select 문을 실행해서 이용해 비교한다.
  3. DbUnit과 Unitils를 함께 사용한다.

정리

데이터베이스와 연관되어 있는 프로그램에 대해 TDD 진행시 가장 효율이 높은 방법으로 1)@BeforeClass를 이용해 트랜잭션을 선언해놓고 ‘입력->수정->삭제’ 순으로 동작시켰던 방식 2)DbUnit, Unitils를 함께 사용하는 방식이 있따.

7.4 안티패턴, 전통적으로 잘못 인식되어 있는 테스트 메소드의 리팩토링

좋은 테스트 케이스는 다음과 같은 규칙을 따른다.

  1. 하나의 테스트 케이스는 외부와 독립적이어야 한다. 따라서 다른 테스트 케이스에 영향을 주거나 받지 않아야 한다.
  2. 하나의 일관된 시나리오를 갖고 있어야 한다.

테스트 메소드는 하나의 일관된 시나리오을 갖고 외부에 독립적이어야 하는데 setUp을 함께 살펴봐야 문맥적으로 하나의 정상적인 테스트 시나리오가 만들어지는 경우가 있다. 중복되는 코드가 존재하면 안된다고 생각할 수 있으나 테스트 케이스 코드는 접근이 조금 다르다. 정련시 중복을 제거하는 것은 맞지만 그 중복되는 부분이 테스트 시나리오의 일부라면 조금 고민해볼 필요가 있다.

This post is licensed under CC BY 4.0 by the author.

6장 단위테스트 지원 라이브러리:Unitils

8장 TDD에 대한 다양한 시각