테스트 코드를 작성했을 때의 장점 중에 더 좋은 설계, 더 낮은 결합도를 가진 코드를 만들 수 있다는 내용이 있었다.
더 낮은 결합도는 보통 내부에서 생성자를 통해 생성하는 것이 아닌 외부로부터 값을 주입 받는 식의 DI를 통해 해결할 수 있다.
그 과정을 실제 코드를 통해 기록을 남겨놓으면 더 이해가 쉬울 것 같아 적어본다.
테스트 상황
0~12 사이의 랜덤한 길이를 가진 패스워드를 생성하는 코드를 테스트 하고싶다.
잘못된 테스트 코드 작성 예시
@DisplayName("패스워드를 초기화한다.")
@Test
void passwordTest(){
//given
User user = new User();
//when
user.initPassword();
//then
assertThat(user.getPassword()).isNotNull();
}
public class User {
private String password;
public String getPassword() {
return password;
}
public void initPassword() {
RandomPasswordGenerator generator = new RandomPasswordGenerator();
String password = passwordGenerator.generatePassword();
if (password.length() >= 8 && password.length() <= 12) {
this.password = password;
}
}
}
위 테스트 코드의 initPassword();는 내부적으로 랜덤한 값을 생성하는 메서드이기 때문에 테스트의 결과는 랜덤하다.
위 코드를 항상 통과하는 테스트로 만드는 방법은 뭐가 있을까?
첫 번째로 Mock 객체를 생성해서 랜덤 값을 지정하는 방법이 있을 것이다. 하지만 이 방법을 사용했을 때는 현재 예시에서 더 좋은 설계로 이어지는 장점을 설명할 수 없기 때문에 두 번째 방법인 랜덤 패스워드를 외부로부터 주입하는 방법에 대해 설명해보겠다.
올바른 테스트 코드 작성 예시
@DisplayName("패스워드를 초기화한다.")
@Test
void passwordTest(){
//given
User user = new User();
//when
user.initPassword(new CollectFixedPasswordGenerator());
//then
assertThat(user.getPassword()).isNotNull();
}
public interface PasswordGenerator {
String generatePassword();
}
public class RandomPasswordGenerator extends org.example.passwordTest.PasswordGenerator{
... 내부 코드
}
public class User {
private String password;
public String getPassword() {
return password;
}
public void initPassword(PasswordGenerator passwordGenerator) {
String password = passwordGenerator.generatePassword();
if (password.length() >= 8 && password.length() <= 12) {
this.password = password;
}
}
}
public class CollectFixedPasswordGenerator implements PasswordGenerator{
@Override
public String generatePassword(){
return "asdfqwer";
}
}
먼저 랜덤한 패스워드를 생성하는 RandomPasswordGenerator를 PasswordGenerator의 구현체로 만들고, initPassword 메서드를 호출할 때 매개변수로 다형성을 활용하기 위해 PasswordGenerator를 주입 받도록 한다.
이렇게 작성된 initPassword는 매개변수로 전달하는 값에 고정된 값을 생성하는 CollectFixedPasswordGenerator를 주입 받으면 고정된 값으로 테스트할 수 있는 안전한 설계가 된다.
또한 PasswordGenerator는 하나의 메서드만을 가지는 인터페이스로 FuncationalInterface를 활용하여 더 간결하게 구현할 수 있다.
FunctionalInterface 적용
@DisplayName("패스워드를 초기화한다.")
@Test
void passwordTest(){
//given
User user = new User();
//when
user.initPassword(()-> "asdfqwer");
//then
assertThat(user.getPassword()).isNotNull();
}
@FunctionalInterface
public interface PasswordGenerator {
String generatePassword();
}
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!