도구 다루기

테스팅 프레임워크

한 여행사의 차세대 예약 시스템 구축 프로젝트에 참여하여 항공 예약 데이터 생성 모듈을 맡게 되었는데, 그중에는 작동 명세가 다음과 같은 모듈 함수가 있었다.
‘승객(passenger) 객체, 항공편(flight) 객체를 입력받은 createReservation은 passengerInformation 프로퍼티가 승객 객체, flightInformation 프로퍼티가 항공편 객체인 새로운 객체를 반환한다.’

1
2
3
4
5
6
function createReservation(passenger, flight) {
return {
passengerInfo: passenger,
flightInfo: flight
}
}

팀 규정상 단위 테스트 없이 제품 코드를 체크인할 수 없으므로 단위 테스트 작성은 반드시 필요하다.
참조할 함수는 이미 구현됐으니 어려운 일이 아니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
describe('createReservation(passenger, flight', function () {
it('주어진 passenger를 passengerInfo 프로퍼티에 할당한다.', function () {
var testPassenger = {
firstName: '길동',
lastName: '홍'
};

var testFlight = {
number: '3443',
carrier: '대한항공',
destination: '울신'
};

var reservation = createReservation(testPassenger, testFlight);
expect(reservation.passengerInfo).toBe(testPassenger);
});

it('주어진 filght를 flightInfo 프로퍼티에 할당한다.', function () {
var testPassenger = {
firstName: '길동',
lastName: '홍'
};

var testFlight = {
number: '3443',
carrier: '대한항공',
destination: '울신'
};

var reservation = createReservation(testPassenger, testFlight);
expect(reservation.flightInfo).toBe(testFlight);
});
});

위의 코드는 재스민 테스팅 프레임워크로 작성한 단위 테스트 코드다.
it 함수 각자는 개별 단위 테스트고(위 코드에서는 2개의 단위 테스트), 이들은 함수에서 반환된 객체의 속성이 적절한지 expect 함수로 검사한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<link data-require="jasmine@*" data-semver="2.0.0"
rel="stylesheet"
href="http://cdn.jsdelivr.net/jasmine/2.0.0/jasmine.css" />
<script data-require="jasmine@*" data-semver="2.0.0"
src="http://cdn.jsdelivr.net/jasmine/2.0.0/jasmine.js">
</script>
<script data-require="jasmine@*" data-semver="2.0.0"
src="http://cdn.jsdelivr.net/jasmine/2.0.0/jasmine-html.js">
</script>
<script data-require="jasmine@*" data-semver="2.0.0"
src="http://cdn.jsdelivr.net/jasmine/2.0.0/boot.js">
</script>
<script src="TestFrameworks_01.js"></script>
<script src="TestFrameworks_01_test.js"></script>
</head>
</html>

위와 같이 html 파일을 추가하고 단위 테스트를 실행한다.
자세히 들여다 보니 단위테스트에 오류가 보인다. 반환된 예약 객체의 속성명은 passengerInformation과 flightInformation이라고 명세에 나와 있는데, passengerInfo와 flightInfo로 잘못 코딩한 것이다. 명세가 아니라 함수 코드의 개발에 따라 테스트를 작성한 탓에 테스트는 기대하는 함수 작동이 아닌, 구현된 함수의 잘못된 실제 작동을 확인한 꼴이다. 명세 기준으로 테스트 코드를 작성했으면 속성명을 틀릴 일이 없었을 것이다.

잘못된 코드 발견하기

TDD는 코드 결함을 최대한 빨리, 곧 코드 생성 직후 감지하며, 작은 기능 하나라도 테스트를 먼저 작성한 뒤, 취소한의 코드만으로 기능을 구현한다.
createReservation 함수로 다시 돌아가 테스트를 먼저 작성하면 어떻게 달라지는지 알아보고자 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
describe('createReservation(passenger, flight)', function () {
it('주어진 passenger를 passengerInformation 프로퍼티에 할당한다.', function () {
var testPassenger = {
firstName: '길동',
lastName: '홍'
};

var testFlight = {
number: '3443',
carrier: '대한항공',
destination: '울신'
};

var reservation = createReservation(testPassenger, testFlight);
expect(reservation.passengerInformation).toBe(testPassenger);
})
});
1
2
3
4
5
6
function createReservation(passenger, flight) {
return {
passengerInfo: passenger,
flightInfo: flight
}
}

위의 코드로 단위 테스트하면 실패하게 된다.
반환 객체의 속성명을 잘못해서 passengerInformation 대신 passengerInfo로 적었다. 속성명을 정정하고 다시 테스트하면 성공한다.

반환 객체의 속성명을 잘못 쓴 실수가 createReservation 함수를 구현한 코드에 잠복해 있지만, 이번에는 테스트를 먼저 작성한 뒤 명세에 따라 테스트를 했으므로 다른 개발자가 통합 테스트를 진행하다가 에러를 즉시 확인하여 조치할 수 있다.

테스트성을 감안하여 설계하기

테스트를 먼저 작성하란 건 코드의 테스트성을 차후에 두고 볼 문제가 아니라 우선적인 주요 관심사로 생각하는 것이다. 어떤 코드의 테스트 용이성과 그 코드의 테스트가 얼마나 잘 이루어졌는지는 직접적인 상관 관계가 있다.

꼭 필요한 코드만 작성하기

TDD 작업 절차를 정리해보자. 작은 기능 하나를 검증하려면 실패하는 테스트를 먼저 작성한 뒤, 테스트를 성공시킬 만큼만 최소한으로 코딩한다. 그 후 내부적으로 구현 세부를 변경하는 리팩토링 과정을 거쳐 개발 중인 코드에서 중복 코드를 들어낸다. 이런 과정을 거치면서 결국 마지막에는 꼭 필요한 코드만 남게 된다.

안전한 유지 보수와 리팩토링

TDD를 실천하면 프로젝트 제품 코드를 대상으로 확실한 단위 테스트 꾸러미를 구축할 수 있다. 예전에 잘 돌아가던 코드가 지금은 제대로 작동하지 않은 회귀 결함은 코드 품질과 믿음성을 떨어뜨리는 요인이다.
여타 보험 정책이 그렇듯, 혜택은 없고 짐만 되는 재발 비용이 발생한다. 단위 테스트의 경우 테스트 꾸러미를 개발/보수하느라 재발 비용이 들어가는데, 보험과 마찬가지로 이 재발 비용을 지불하는 부담에서 벗어나는 시점이 온다.
종합적인 단위 테스트 꾸러미가 마련된 제품 코드를 확장 또는 보수할 때도 비슷한 안도감을 느낄 수 있다. 실수로 다른 코드를 건드리지 않았다는 확신을 하고 코드 일부를 변경할 수 있기 때문이다.

실행 가능한 명세

TDD 실천 결과, 탄탄하게 구축된 단위 테스트 꾸러미는 테스트 대상 코드의 실행 가능한 명세 역할도 한다. 단위 테스팅 프레임워크인 재스민은 행위 기반(behavior-based) 으로 테스트를 구성한다. 재스민에서 스펙이라 부르는 개별 테스트는, 테스트하여 검증할 작동 로직을 일상 문장으로 표현하면서 시작한다.
createReservation 함수를 예로 들면, 재스민으로 단위 테스트한 결과 메시지를 보고 이 함수가 무슨 일을 하는지 큰 그림을 그려볼 수 있다. createReservation이 하는 일을 굳이 코드를 읽고 분석하지 않아도 단위 테스트가 죄다 알려주는 셈이다.

참조: 자바스크립트 패턴과 테스트
github

댓글

You need to set client_id and slot_id to show this AD unit. Please set it in _config.yml.