본문 바로가기

IT/책읽는 개발자

[레거시 코드 활용 전략] CH3 감지와 분리

반응형

 

2020/02/03 - [IT/책읽는 개발자] - [레거시 코드 활용 전략] CH1 소프트웨어 변경

2020/02/04 - [IT/책읽는 개발자] - [레거시 코드 활용 전략] CH2 피드백 활용

ch 3 - 감지와 분리

이상적인 환경이라면 변경 작업을 하기 전 특별히 할 일이 없다.

테스트를 하고 싶은 대상을 테스트 코드 내에서 객체를 생성해 작업을 하면 끝이다.

 

하지만 불행하게도 클래스들의 의존 관계들 때문에 이것은 거의 불가능하다.

의존관계를 제거해야 하는 이유는 무엇일까?

크게 두 가지로 볼 수 있다.

  1. 감지 : 코드 내에서 계산된 값에 접근하여 변경이나 값을 확인할 수 없을 때
  2. 분리 : 코드를 테스트 코드 내에서 실행할 수 없을 때 코드 분리를 위해

 

예제 코드

public class NetworkBridge {
    public NetworkBridge(EndPoint[] endpoints){
        ...
    }

    public void formRouting(String sourceId, String destId){
        ...
    }
...
}

위 코드에 대한 간략한 설명은

  1. NetworkBridge 클래스는 EndPoint 배열을 입력받고, 하드웨어에 전달하여 트래픽을 관리한다.
  2. 사용자는 해당 클래스의 메소드를 사용하여 종점 간 트래픽 경로를 제어할 수 있다.
  3. NetworkBridge는 이러한 작업들을 진행하기 위해 전달받은 EndPoint 설정을 변경한다.

해당 테스트를 진행하기 위해 무엇이 문제일까?

  1. 테스트를 하기 위해 실제 하드웨어가 필요한가?
  2. NetworkBridge 내부에서 통신을 위해 EndPoint 객체에 무슨 작업을 했을까? → 블랙박스

이 예제는 메소드가 호출된 때 다른 곳에 끼치는 영향을 알 수 없고, 실제 어플리캐이션과 분리해서 테스트도 힘들다.

 

분리에 사용되는 기법들은 아주 다양하다. 그렇기 때문에 해당 글에서는 감지의 경우에 대부분 사용되는 위장 객체 사용을 설명한다.

 

협업 클래스 위장하기

특정 코드만 독립적으로 실행하기는 매우 어렵다.

그 이유는 해당 의존성이 우리가 수행하는 작업의 영향을 감지할 수 있는 위치일 때가 많기 때문이다.

따라서 그 의존성을 가진 코드를 대체하여 변경 대상을 테스트하는 루틴을 작성할 수 있다.

이것을 위장 객체라고 부른다.

위장 객체

만약 의존성이 있는 객체가 우리 로직의 영향을 감지할 수 있는 영역이라면, 이 협업 객체를 모방하는 객체를 만들어 보자.

 

예를 들어서 Interviewer(면접관)라는 클래스가 있다.

해당 클래스는 Applicant(지원자)를 evaluation()이라는 메소드를 호출하여 평가하고 해당 지원자에게 메일을 전송한다.

이때 해당 평가자에게 메일을 정상적으로 발송하는지 어떻게 테스트를 진행할 수 있을까?

만약 메일을 전송하는 부분이 Interviewer 내부에 MailSender 클래스에서 발송한다고 하면 아래와 같이 설계가 가능하다.

이렇게 된다면 MailSender 클래스뿐만 아니라 FakeSender 클래스도 사용할 수 있게 된다.

FakeSender의 특징이라면 평가 단계에서 Sender 내부에서 어떤 동작이 일어나는지에 대한 로직을 만들 수 있다는 점이다.

 

public interface Sender {
    void send(String text);
}

FakeSender 와 MailSender 모두 send라는 인터페이스를 구현한다.

 

public class Interviewer {
    private Sender sender;

    public Interviewer(Sender sender){
        this.sender = sender;
    }

    public void evaluation(String step, Boolean isPass){
        ...
        String text = "You" + ((isPass) ? "succeeded" : "failed") 
                                            + "at the " + step + " interview."
        sender.send(text);
        ...
    }
}

evaluation 메소드는 sender 변수의 send 메소드를 호출한다.

하지만 동작에 대한 구현은 전달된 Sender 가 무엇인지에 따라 달라진다.

다음 코드는 어떤 내용에 메일이 전달 됬는지 확인할 수 있는 테스트 루틴이다.

public class InterviewerTest{
    public void testSendMail(){
        FakeSender sender = new FakeSender();
        Interviewer interviewer = new Interviewer(sender);

        interviewer.evaluation("2차", true);
        assertEquals("You succeeded at the 2차 interview.", sender.getSendText());
    }
}

 

FakeSender의 구현은 아래와 같다.

public class FakeSender implements Sender {
    private String sendText;

    public void send(String text) {
        sendText = text;
    }

    public String getSendText() {
        return sendText;
    }
}

 

FakeSender는 우리에게 어떤 도움을 줄까?

해당 테스트 루틴을 사용한다면 실제 메일이 발송되었을 때 어떤 메일 내용이 전달되는지 확인할 수 있다.

 

그렇다면 해당 테스트가 정말 진짜 테스트일까?

만약 메일을 전송하는 메일 에이전트가 잘못 동작한다면 우리는 그 오류를 해당 테스트를 통해 잡아낼 수 있을까?

물론 그런 오류는 잡아낼 수 없다. 그렇지만 이 테스트가 거짓 테스트라는 말은 아니다.

설령 실제 메일 에이전트와 동일한 테스트 환경을 구축한다고 해서 다른 종류의 메일 에이전트의 동작을 보장할 수는 없다.

 

우리는 여기서 해당 테스트의 목적을 다시 상기할 필요가 있다.

해당 테스트는 우리가 메일을 '전송' 하는 시점에 발송되는 내용(계산된 값)을 '감지' 하기 위해 해당 테스트를 작성했다.

이러한 테스트는 적어도 버그 및 오류의 원인이 Interviewer 클래스에게 있는지를 확인할 수 있게 해준다.

이러한 테스트를 통해 우리는 범위를 좁혀 시간을 절약할 수 있게 되는 것이다.

반응형