* 사실 저도 잘 몰?루
공부는 블로그가 아니라 레퍼런스를 보면서 합시다 ^^~~~~ *
오늘은 11월 2일.. 아직 파이널 프로젝트 시작 일주일 전이다.
이 글이 올라갈 때는 이미 파이널 프로젝트도 끝나가는 시점이겠군.
지난 프로젝트에서 시간에 쫓기며 테스트를 꼼꼼하게 챙기지 못한 부분들에서 줄줄 흘러나오는 버그들의 홍수를 겪고, 단위테스트의 중요성을 몸소 깨달아 지금은 단위테스트를 최대한 신경 쓰려고 노력하고 있다.
(물론, 단위테스트만 중요한 것이 아니라 통합테스트도 엄청 중요하다.)
기본적으로 TDD를 중심으로 진행하되 여건상 일부는 코드를 먼저 작성하고 있는데, 확실히 TDD가 좋은 것 같다.
TDD는 로직을 만들고 있는 것이나 다름 없이 깨문에 계속해서 생각하고 설계와 맞춰가며, 자연스럽게 해당 로직에서 필요한 부분만 테스트하게 되는데.. 로직을 먼저 작성하고 테스트를 하면 그냥 귀찮은 노가다가 되면서 아차하는 사이 이상한 테스트 코드를 작성하고 있는 것이다.
아래는 1시간 전에 내가 점심 먹고 노곤한 기운에 작성한 테스트 코드다.
맞다, 밑밥이고 변명이다..
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
|
class UserPasswordManagerTest {
UserPasswordManager userPasswordManager;
PasswordEncoder passwordEncoder;
User user1;
User user2;
@BeforeEach
public void init() {
passwordEncoder = new BCryptPasswordEncoder();
userPasswordManager = new UserPasswordManager(passwordEncoder);
user1 = User.builder().password(PASSWORD1).build();
user2 = User.builder().password(PASSWORD2).build();
}
@Test
@DisplayName("패스워드 암호화 전/후의 패스워드는 일치하지 않다.")
public void test1() {
//given when
userPasswordManager.encryptUserPassword(user1);
//then
assertThat(user1.getPassword()).isNotEqualTo(PASSWORD1);
}
@Test
@DisplayName("다른 패스워드를 암호화하고 equals 비교하면 false를 반환한다.")
public void test2() {
//given
userPasswordManager.encryptUserPassword(user1);
userPasswordManager.encryptUserPassword(user2);
//when then
assertThat(user1.getPassword()).isNotEqualTo(user2.getPassword());
}
@Test
@DisplayName("같은 패스워드를 암호화하고 equals 비교하면 false를 반환한다.")
public void test3() {
//given
String password1 = userPasswordManager.encryptUserPassword(user1);
String password2 = userPasswordManager.encryptUserPassword(user1);
//when then
assertThat(password1).isNotEqualTo(password2);
}
@Test
@DisplayName("패스워드를 암호화하고 compareToUserPassword로 다른 평문의 패스워드와 비교하면 false를 반환한다.")
public void test4() {
//given
userPasswordManager.encryptUserPassword(user1);
//when
boolean result = userPasswordManager.compareUserPassword(user1, user2);
//then
assertThat(result).isFalse();
}
@Test
@DisplayName("패스워드를 암호화하고 compareToUserPasswor로 같은 평문의 패스워드와 비교하면 true를 반환한다.")
public void test5() {
//given
User same_password_user = User
.builder()
.password(PASSWORD1)
.build();
userPasswordManager.encryptUserPassword(user1);
//when
boolean result = userPasswordManager.compareUserPassword(user1, same_password_user);
//then
assertThat(result).isTrue();
}
}
|
cs |
현재 이 테스트코드는 User의 메서드를 테스트하면서 PasswordEncoder를 테스트하면서 징검다리 역할인 UserPasswordManager까지 모두 한 번에 테스트하고 있다.
사실 지금도 (적어도 내 생각에는) 썩 나쁘지 않고, 4주짜리 파이널 프로젝트에서 이 정도의 결합도를 가진 테스트 코드가 구조의 변경으로 망가질 일은 없을 것 같지만, 잘못된 것을 알고 있기 때문에 기분이 나쁘다.
기분이 나쁘면 바꿔야한다.
시간이 오래 걸리는 작업도 아니니 빠르게 고쳐보자.
우선 PasswordEncoder에 대한 테스트부터 작성하겠다.
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
|
public class PasswordEncoderTest {
PasswordEncoder passwordEncoder;
@BeforeEach
public void init() {
passwordEncoder = new BCryptPasswordEncoder();
}
@Test
@DisplayName("패스워드를 인코드하면 전/후의 값이 다르다.")
public void test1() {
//given
String encodedPwd = passwordEncoder.encode(PASSWORD1);
//when
boolean result = PASSWORD1.matches(encodedPwd);
//then
assertThat(result).isFalse();
}
@Test
@DisplayName("각자 다른 평문을 암호화하고 mathces 비교하면 false를 반환한다")
public void test2() {
//given
String encodedPwd = passwordEncoder.encode(PASSWORD1);
String encodedPwd2 = passwordEncoder.encode(PASSWORD2);
//when
boolean result = encodedPwd.matches(encodedPwd2);
//then
assertThat(result).isFalse();
}
@Test
@DisplayName("서로 같은 평문을 암호화하고 matches 비교하면 false를 반환한다.")
public void test3() {
//given
String encodedPwd = passwordEncoder.encode(PASSWORD1);
String encodedPwd2 = passwordEncoder.encode(PASSWORD1);
//when
boolean result = encodedPwd.matches(encodedPwd2);
//then
assertThat(result).isFalse();
}
@Test
@DisplayName("평문을 암호화하고 다른 평문과 passwordEncoder.matches로 비교하면 false를 반환한다.")
public void test4() {
//given
String encodedPwd = passwordEncoder.encode(PASSWORD1);
//when
boolean result = passwordEncoder.matches(PASSWORD2, encodedPwd);
//then
assertThat(result).isFalse();
}
@Test
@DisplayName("평문을 암호화하고 같은 평문과 passwordEncoder.matches로 비교하면 true를 반환한다.")
public void test5() {
//given
String encodedPwd = passwordEncoder.encode(PASSWORD1);
//when
boolean result = passwordEncoder.matches(PASSWORD1, encodedPwd);
//then
assertThat(result).isFalse();
}
}
|
cs |
이 테스트 코드를 보면 위의 테스트코드는 사실상 PasswordEncoder에 대한 테스트나 다름없었다는 사실을 알 수 있다.
이제 passwordEncoder에 대한 테스트는 끝났으니, UserPaswordManager와 User의 테스트가 필요하다.
약간 새는 얘기지만 혹시 같은 평문을 암호화하고 비교했는데 다른 이유가 궁금한 사람이 있다면?
지난번에 내가 작성한 글을 읽어보자 => https://7357.tistory.com/297
이번에는 User를 테스트해보겠다.
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
|
@ExtendWith(MockitoExtension.class)
class UserTest {
@Mock
PasswordEncoder passwordEncoder;
private String ENCODED_PASSWORD;
@BeforeEach
public void init() {
ENCODED_PASSWORD = "ENCODED_PASSWORD";
}
@Test
@DisplayName("encryptionPassword/ 패스워드 암호화 실행한 후 패스워드는 이전과 같지 않다.")
public void test1() {
//given
User user = User
.builder()
.password(PASSWORD1)
.build();
given(passwordEncoder.encode(user.getPassword())).willReturn(ENCODED_PASSWORD);
//when
user.encryptPassword(passwordEncoder);
//then
assertThat(user.getPassword()).isNotEqualTo(PASSWORD1);
}
@Test
@DisplayName("encryptionPassword/ 암호화 실행 후 패스워드는 passwordEncoder로 암호화된 값과 같다.")
public void test2() {
//given
User user = User
.builder()
.password(PASSWORD1)
.build();
given(passwordEncoder.encode(user.getPassword())).willReturn(ENCODED_PASSWORD);
//when
user.encryptPassword(passwordEncoder);
//then
assertThat(user.getPassword()).isEqualTo(ENCODED_PASSWORD);
}
@Test
@DisplayName("comparePassword/ 다른 유저의 패스워드와 비교했을 때, 패스워드가 다르다면 false를 반환한다.")
public void test3() {
//given
User user = User
.builder()
.password(PASSWORD1)
.build();
User user2 = User
.builder()
.password(PASSWORD2)
.build();
given(passwordEncoder.matches(PASSWORD2, PASSWORD1)).willReturn(Boolean.FALSE);
//when
boolean result = user.comparePassword(passwordEncoder, user2);
//then
assertThat(result).isFalse();
}
@Test
@DisplayName("comparePassword/ 다른 유저의 패스워드와 비교했을 때, 패스워드가 같다면 true를 반환한다.")
public void test4() {
//given
User user = User
.builder()
.password(PASSWORD1)
.build();
User user2 = User
.builder()
.password(PASSWORD1)
.build();
given(passwordEncoder.matches(PASSWORD1, PASSWORD1)).willReturn(Boolean.TRUE);
//when
boolean result = user.comparePassword(passwordEncoder, user2);
//then
assertThat(result).isTrue();
}
}
|
cs |
User 입장에서 PasswordEncoder의 실제 동작 같은 것은 아무런 상관이 없다.
상관이 없는 정도가 아니라 아예 모른다.
그냥 passwordEncoder에게 내 정보를 능동적으로 제공해주고, 협력으로 얻은 결과를 스스로 반환하거나 자신의 데이터에 반영할 뿐이다.
따라서 이번 테스트는 "User가 psswordEncoder에서 이런 로직에서 이런 결과를 반환한다고 가정할 때, 우리가 예측한대로 동작하는가?"를 테스트하는 것이다.
따라서 우리의 목적에 맞게 테스트를 완료했다고 볼 수 있겠다.
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
|
@ExtendWith(MockitoExtension.class)
class UserPasswordManagerTest {
UserPasswordManager userPasswordManager;
@Mock
PasswordEncoder passwordEncoder;
User user1;
User user2;
@BeforeEach
public void init() {
userPasswordManager = new UserPasswordManager(passwordEncoder);
user1 = User.builder().password(PASSWORD1).build();
user2 = User.builder().password(PASSWORD2).build();
}
@Test
@DisplayName("compareUserPassword/ passwordEncoder가 주어진 인자로 matches를 1회 수행한다")
public void test1() {
//given
given(passwordEncoder.matches(anyString(), anyString())).willReturn(true);
//when
userPasswordManager.compareUserPassword(user1, user2);
//then
verify(passwordEncoder, times(ONE)).matches(anyString(), anyString());
}
@Test
@DisplayName("compareUserPassword/ passwordEncoder가 주어진 인자로 encode를 1회 수행한다")
public void test2() {
//given
given(passwordEncoder.encode(anyString())).willReturn(anyString());
//when
userPasswordManager.encryptUserPassword(user1);
//then
verify(passwordEncoder, times(ONE)).encode(anyString());
}
}
|
cs |
늘 가장 난해한 중간 다리 역할을 해주는 친구의 테스트다.
난 UserPasswordManager는 passwordEncoder와 User의 어댑터 역할이라고 생각했다.
따라서 UserPasswordManager 입장에서 실제로 어떤 결과가 반환되는지는 아무 상관이 없으며, 메서드 호출 시마다 주어진 인자가 내가 정해놓은 방식대로 움직이고 있는지만 확인하면 된다고 판단하고 이렇게 테스트했다.
음.. 뭔가 더 할만한 게 있을 것 같기도 한데 잘 모르겠다.
오늘은 여기서 끝.
'Language & Framework > 삽질기록' 카테고리의 다른 글
삽질 기록 (14) JPA DB테스트 시 @CreatedDate Mocking해서 테스트하기 (2) | 2022.12.09 |
---|---|
삽질기록(12) 레디스 적용된 코드 어떻게든 꾸역꾸역 단위테스트 하기 (0) | 2022.12.01 |
삽질 기록 (11) 로컬에서는 잘만 되는 WebSocket 채팅 EC2에서는 외않뒈? (0) | 2022.11.23 |
삽질 기록(9) PasswordEncoder 테스트는 왜 실패하는 걸까? 해시 함수와 암호화 알고리즘 간단하게 알아보기 (0) | 2022.11.20 |
삽질 기록 (8) 삼항연산자의 기이한 NullPointerException을 겪고 있다면.. 이리오십쇼 (0) | 2022.11.18 |