[자바스크립트 닌자] 자바스크립트 테스트 스위트의 기본

테스트 스위트

테스트 스위트의 주된 목적은 개별 테스트를 묶어 하나의 자원으로 제공함으로써 여러 테스트를 한 번에 실행할 수 있게 그리고 반복해서 간단히 실행할 수 있게 하는 것이다.

이러한 테스트 스위트를 직접 만들어 봄으로써 직접 감각을 익혀봐야 한다.

검증 조건

단위 테스트 프렘이워크의 핵심은 검증 메서드로, 이 메서드의 이름은 일반적으로 assert()다. 해당 메서드는 항상 검증의 전제가 되는 표현 하나와 검증의 목적을 설명하는 인자를 받는다. 해당 표현 값이 “참”이거나 “참이 될 수 있는 값”이면 검증 조건을 통과하게 된다.

이러한 개념을 코드로 구현해보자.

소스코드

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
<!DOCTYPE html>
<html lang="en">
	<head>
		<title>Test Suite</title>
		<script>
			function assert(value, desc) {
				var li = document.createElement('li');
				li.className = value ? 'pass' : 'fail';
				li.appendChild(document.createTextNode(desc));
				document.getElementById('results').appendChild(li);
			}
			window.onload = function () {
				assert(true, 'The test suite is running');
				assert(false, 'Fail!');
			};
		</script>
		<style>
			#results li.pass {
				color: green;
			}
			#results li.fail {
				color: red;
			}
		</style>
	</head>
	<body>
		<ul id="results"></ul>
	</body>
</html>

코드설명

위의 방식은 검증 표현식 매개변수의 값에 따라, pass 또는 fail이라고 정의된 CSS 클래스를 할당한다. assert 함수는 이처럼 단순한 로직을 지니지만, 여러 코드 조각들을 테스트하고 정상적으로 동작하는지 확인할 수 있다.

테스트 그룹

이러한 검증 과정을 그룹으로 묶으면, 테스트 그룹 내의 조건이 하나라도 실패하면 전체 테스트 그룹이 실패하는 로직도 설계할수가 있다.

그룹 테스트를 이용하는 이유는 테스트 그룹 내에 실패한 테스트가 있을 경우, 테스트 그룹을 확장/축소하거나 필터링하는 것이 가능하기 때문이다.

위의 코드를 변형하여 테스트 그룹을 구현해보자.

소스코드

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<!DOCTYPE html>
<html lang="en">
	<head>
		<title>Test Suite</title>
		<script>
			(function () {
				var results;
				this.assert = function assert(value, desc) {
					var li = document.createElement('li');
					li.className = value ? 'pass' : 'fail';
					li.appendChild(document.createTextNode(desc));
					results.appendChild(li);
					if (!value) {
						li.parentNode.parentNode.className = 'fail';
					}
					return li;
				};
				this.test = function test(name, fn) {
					results = document.getElementById('results');
					results = assert(true, name).appendChild(
						document.createElement('ul')
					);
					fn();
				};
			})();
			window.onload = function () {
				test('A test.', function () {
					assert(true, 'First assertion completed');
					assert(true, 'Second assertion completed');
					assert(true, 'Third assertion completed');
				});
				test('Another test.', function () {
					assert(true, 'First test completed');
					assert(false, 'Second test failed');
					assert(true, 'Third assertion completed');
				});
				test('A third test.', function () {
					assert(null, 'fail');
					assert(5, 'pass');
				});
			};
		</script>
		<style>
			#results li.pass {
				color: green;
			}
			#results li.fail {
				color: red;
			}
		</style>
	</head>
	<body>
		<ul id="results"></ul>
	</body>
</html>

코드설명

위의 코드는 results 변수를 추가함으로써 test 그룹 내의 assert 별로 테스트를 관리하여 그룹 테스트의 검증 조건을 판단해주는 역할을 하고있다. 실제로 실행해보면 Another test와 A third test 그룹은 실패라고 나타나게 된다.

비동기 테스트

비동기 테스트를 다루기 위해서는 다음 단계를 따라야한다.

  1. 동일한 비동기 연산에서 사용해야 하는 검증 조건은 같은 테스트 그룹으로 묶어야 한다.
  2. 각 테스트 그룹은 하나의 큐에 존재해야 하고, 이전 테스트 그룹이 모두 종료한 뒤에 실행되어야 한다.

비동기 코드는 좀 이해하기 어려울 수 있지만 한번 확인해보자.

소스코드

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
<!DOCTYPE html>
<html lang="en">
	<head>
		<title>Test Suite</title>
		<script>
			(function () {
				var queue = [],
					paused = false,
					results;
				this.test = function (name, fn) {
					queue.push(function () {
						results = document.getElementById('results');
						results = assert(true, name).appendChild(
							document.createElement('ul')
						);
						fn();
					});
					runTest();
				};
				this.pause = function () {
					paused = true;
				};
				this.resume = function () {
					paused = false;
					setTimeout(runTest, 1);
				};
				function runTest() {
					if (!paused && queue.length) {
						queue.shift()();
						if (!paused) {
							resume();
						}
					}
				}
				this.assert = function assert(value, desc) {
					var li = document.createElement('li');
					li.className = value ? 'pass' : 'fail';
					li.appendChild(document.createTextNode(desc));
					results.appendChild(li);
					if (!value) {
						li.parentNode.parentNode.className = 'fail';
					}
					return li;
				};
			})();
			window.onload = function () {
				test('Async Test #1', function () {
					pause();
					setTimeout(function () {
						assert(true, 'First test completed');
						resume();
					}, 1000);
				});
				test('Async Test #2', function () {
					pause();
					setTimeout(function () {
						assert(true, 'Second test completed');
						resume();
					}, 1000);
				});
			};
		</script>
		<style>
			#results li.pass {
				color: green;
			}
			#results li.fail {
				color: red;
			}
		</style>
	</head>
	<body>
		<ul id="results"></ul>
	</body>
</html>

코드설명

위의 코드는 외부에서 사용 가능한 함수가 3개 있다.

  1. test() : 다수의 검증 조건을 가지고 있는 함수를 인자로 받고, 이 함수를 테스트 실행을 하기 위한 큐에 저장한다. 함수가 가지고 있는 검증 조건은 동기나 비동기로 실행된다.
  2. pause() : 테스트 함수 안에서 호출되며, 현재 테스트 그룹의 실행이 완료될 때까지, 테스트 스위트에 실행 중인 전체 테스트를 정지하라고 알려준다.
  3. resume() : 테스트를 재개하고, 이전 테스트 코드가 오래 실행되어 전체 코드가 멈추는 것을 방지하기 위해서 잠시 동안의 딜레이를 준 다음 테스트를 시작한다.

내부적인 구현 함수는 runTest() 이며 테스트가 큐에 저장되거나 빠질 때 호출된다. 테스트 스위트가 정지되지 않았고, 큐 안에 테스트가 있다면, 테스트를 큐에서 빼서 실행한다. 게다가 테스트 그룹의 실행이 종료된 후에, runTest()는 테스트 스위트가 현재 정지되어 있는지를 검사하고, 그렇지 않다면(테스트 그룹 안에 비동기 테스트만 실행되고 있다면), runTest()는 다음 테스트 그룹을 실행하기 시작한다.


정리

테스트 프레임워크를 이해하기 위해 검증 함수를 직접 만들어보고 비동기 테스트 케이스를 다루어 보았다. 해당 기술은 자바스크립트 개발에 매우 중요한 부분이라고 존 레식은 말한다.