정의


유닛 테스트(unit test)는 컴퓨터 프로그래밍에서 소스 코드의 특정 모듈이 의도된 대로 정확히 작동하는지 검증하는 절차다. 즉, 모든 함수와 메소드에 대한 테스트 케이스(Test case)를 작성하는 절차를 말한다. 이를 통해서 언제라도 코드 변경으로 인해 문제가 발생할 경우, 단시간 내에 이를 파악하고 바로 잡을 수 있도록 해준다. 이상적으로, 각 테스트 케이스는 서로 분리되어야 한다. 이를 위해 **가짜 객체(Mock object)**를 생성하는 것도 좋은 방법이다. 유닛 테스트는 (일반적인 테스트와 달리) 개발자(developer) 뿐만 아니라 보다 더 심도있는 테스트를 위해 테스터(tester)에 의해 수행되기도 한다. - 위키피디아

Bottom-Up 방식


바텀업 { width: 200px; }

작은 모듈(Unit)에서부터 출발하여 점차 큰 모듈로 통합(Integration)해간다.

작은 모듈에서 불확실성을 제거하면 개별적인 모듈에서 발생하는 문제를 없앨 수 있다.

유닛 테스트의 독립성


각각의 유닛들은 다른 유닛에 영향을 주면 안된다.

모듈이 독립적으로 수행될 수 있는 수준의 개념으로 테스트가 설계되어야 한다.

서로 영향을 끼치는 동작은 보다 상위단계(통합 테스트, 기능 테스트)에서 진행한다.

유닛 테스트의 자동화


유닛 테스트는 신속하고, 반복할 수 있어야 한다.

따라서, 자동화 도구를 이용하여 테스트를 최대한 간결화 시킨다.

가짜 객체 (Mock Object)


유닛 테스트는 독립적으로 수행되어야 하기 때문에 다른 모듈과 통신이 불가능하다.

그래서 통신에 필요한 가짜 객체(mock)를 만들어 주입한다.

mock을 주입하여 원하는 결과를 얻는 과정을 stub이라고 한다.

Stub

stub을 하게되면,

  1. 기능이 불안정한 단계에서 실제 database의 오염을 피할 수 있다.
  2. 실제 database 모듈이 불완전하더라도 모듈 테스트를 진행할 수 있다.
  3. 실제 database와 통합 시 문제 발생 확률이 감소한다.

어떤 프로젝트에서 적용해야하는가?


유닛테스트는 미래에 생길 문제를 사전에 방지하기 위해서 작은 단위로 테스트한다.

즉, 개발 단계에서 시간을 더 들여, 유지보수 시간을 줄여주기 위한 목적을 가지고 있다.

따라서, 프로젝트 규모가 거대하고 시스템이 복잡한 경우 유닛테스트를 하는게 좋다.

하지만, 프로젝트 규모가 크지 않다면 유닛테스트는 오히려 피곤한 작업이 될 수 있다.

JavaScript 예제 (Jest)


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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
const { createUser } = require('../src/registration')
const { internet, phone, address, random } = require('faker')
const RandExp = require('randexp')

function generateStrongPassword() {
const strongPasswordPattern =
/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[*.!@$%^&(){}\][:;<>,.?/~_+\-=|]).{8,16}$/
let password = null

do {
password = new RandExp(strongPasswordPattern).gen()
} while (!password.match(strongPasswordPattern))

return password
}

describe('사용자가 데이터를 예상대로 보낼 때', () => {
test('필수항목 + 선택항목 - User', async () => {
const user = await createUser({
email: internet.email(),
password: generateStrongPassword(),
nick: internet.userName(),
phone: phone.phoneNumber('010-####-####'),
postalCode: address.zipCode('#####'),
})
expect(user).not.toBeNull()
})
test('필수항목 - User', async () => {
const user = await createUser({
email: internet.email(),
password: generateStrongPassword(),
nick: internet.userName(),
phone: '',
postalCode: '',
})
expect(user).not.toBeNull()
})
})

describe('사용자가 데이터를 누락했을 때', () => {
test('이메일 필드를 보내지 않았을 때 - null', async () => {
const user = await createUser({
email: '',
password: generateStrongPassword(),
nick: internet.userName(),
phone: phone.phoneNumber('010-####-####'),
postalCode: address.zipCode('#####'),
})
expect(user).toBeNull()
})
test('비밀번호 필드를 보내지 않았을 때 - null', async () => {
const user = await createUser({
email: internet.email(),
password: '',
nick: internet.userName(),
phone: phone.phoneNumber('010-####-####'),
postalCode: address.zipCode('#####'),
})
expect(user).toBeNull()
})
test('닉네임 필드를 보내지 않았을 때 - null', async () => {
const user = await createUser({
email: internet.email(),
password: generateStrongPassword(),
nick: '',
phone: phone.phoneNumber('010-####-####'),
postalCode: address.zipCode('#####'),
})
expect(user).toBeNull()
})
test('모바일 필드를 보내지 않았을 때 - User', async () => {
const user = await createUser({
email: internet.email(),
password: generateStrongPassword(),
nick: internet.userName(),
phone: '',
postalCode: address.zipCode('#####'),
})
expect(user).not.toBeNull()
})
test('우편번호 필드를 보내지 않았을 때 - User', async () => {
const user = await createUser({
email: internet.email(),
password: generateStrongPassword(),
nick: internet.userName(),
phone: phone.phoneNumber('010-####-####'),
postalCode: '',
})
expect(user).not.toBeNull()
})
})

describe('사용자가 데이터를 부실하게 보낼 때', () => {
test('이메일 형식이 잘못되었을 때 - null', async () => {
const user = await createUser({
email: random.alphaNumeric(10),
password: generateStrongPassword(),
nick: internet.userName(),
phone: phone.phoneNumber('010-####-####'),
postalCode: address.zipCode('#####'),
})
expect(user).toBeNull()
})
test('비밀번호의 강도가 너무 약할 때 - null', async () => {
const user = await createUser({
email: internet.email(),
password: random.alphaNumeric(10),
nick: internet.userName(),
phone: phone.phoneNumber('010-####-####'),
postalCode: address.zipCode('#####'),
})
expect(user).toBeNull()
})
})

MOCK

참고


https://mangkyu.tistory.com/143