[TEST] 테스트 코드 작성 시 더 낮은 결합도를 가진 코드 만들기

테스트 코드를 작성했을 때의 장점 중에 더 좋은 설계, 더 낮은 결합도를 가진 코드를 만들 수 있다는 내용이 있었다.

더 낮은 결합도는 보통 내부에서 생성자를 통해 생성하는 것이 아닌 외부로부터 값을 주입 받는 식의 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();
}