주요글: 도커 시작하기
반응형

개발 중인 서비스에서 통합 테스트를 수행하려면 외부 시스템과의 상호 작용을 통해서만 정상적으로 동작하는 기능이 존재한다. 다음은 그 예이다.(E는 외부 시스템과 연동이 필요한 기능을 의미)

  • 접수함(E) > [배관유무확인요청1상태] > 알림벨(E) > [배관유무확인요청2상태] > 배관있음(E) > [표시/회합 결정대기상태]
여기서 특정 데이터를 표시/회합결정대기상태로 만들려면 내부 시스템의 데이터만 바꿔서는 안 된다. 외부에 있는 시스템의 기능을 순차적으로 실행해야 해당 상태로 바꿀 수 있다. 접수함 행위를 외부 시스템에서 하지 않으면 [배관유무확인요청1상태]로 바뀌지 않는다.

외부 시스템과 연동없이 내부 데이터만 [배관유무확인요청1상태]로 바꿔 알림벨 기능을 통합 테스트하면 정상 동작하지 않는다. 외부 시스템의 데이터 상태가 유효하지 않아 외부 시스템이 에러를 발생하기 때문이다.


통합 테스트나 QA를 하려면 수시로 데이터를 특정 상태로 변경해야 했다. 특정 상태로 변경하려면 올바른 순서대로 기능을 실행해야했는데, 이를 위해 다음과 같이 상태를 변경할 수 있는 코드를 테스트 영역에 추가했다.


EocsTransitionRunner runner = ...;

EocsTransitions trans = EocsTransitionsBuilder.builder()

        .receive()

        .alimBellOfNormalJobgu()

        .assignBlock(...)

        .pipeYes()

        .needMeetJobpr20()

        .lipynA()

        .build();


runner.run(trans, jupno, empId);


상태를 변경하는 것을 Transition(전이)로 표현했고, EocsTransitions은 특정 상태로 가기 위한 Transition 목록을 담는다. 메서드 호출로 전이를 표현했고 상태에 따라 알맞은 전이 목록을 생성하도록 했다.


예를 들어, receive() 메서드는 접수를 의미하고, receive() 다음에는 alimBellOfNormalJob()이나 alimBellOfSmallOrEmergent()만 호출할 수 있게 했다. alimBellOfNormalJob() 메서드 호출 다음에는 assignBlock()만 호출 가능하고, 이어서 pipeYes()나 pipeNo()만 호출 가능하게 했다.


EocsTransitionsBuilder는 상태 전이를 어떻게 할지를 기술하는 EocsTransitions를 생성하고 상태 전이는 EocsTransitionRunner라는 별도의 실행기로 처리했다. 그래서 EocsTransitionRunner#run()을 호출하지 않으면 실제 상태 전이는 발생하지 않는다.


EocsTransitions과 EocsTransition


EocsTransitions과 EocsTransition은 간단한 전이 과정을 담기 위한 간단한 클래스이다.


EocsTransition은 다음과 같다.


public class Transition {


    private TranstionType type;

    private Map<String, String> props = new HashMap<>();

    

    public Transition(TranstionType type) {

        this.type = type;

    }

    

    public TranstionType type() {

        return type;

    }


    public Transition addProp(String prop, String value) {

        props.put(prop, value);

        return this;

    }


    public String getProp(String prop) {

        return props.get(prop);

    }


    public String getPropOrEmpty(String prop) {

        String value = getProp(prop);

        return value == null ? "" : value;

    }

}


상태 전이에 따라 추가적인 데이터가 필요한데 이를 props라는 Map에 담도록 했다.


TransitionType은 열거 타입으로 각 전이를 값으로 표현한다.


public enum TranstionType {


    RECEIVE, 

    ALIMBELL, ALIMBELL_OF_SMALL_OR_EMERGENT,

    ASSIGN_BLOCK,

    PIPE_Y, PIPE_N, PIPE_S, PIPE_T,

    JOBPR_20, JOBPR_40, JOBPR_50,

    LIPYN_A, LIPYN_B, LIPYN_C, LIPYN_D, LIPYN_Y, LIPYN_N, 

    

    EOCS_POS_DISP, EOCS_COMPLETE,

}


EocsTransitions는 Transition 목록을 담는 간단한 클래스이다.


public class EocsTransitions {


    private List<Transition> transitions;


    public EocsTransitions(List<Transition> transitions) {

        this.transitions = Collections.unmodifiableList(transitions);

    }


    public List<Transition> transitions() {

        return transitions;

    }

}


상태 전이 생성 위한 EocsTransitionsBuilder와 관련 클래스


EocsTransitionsBuilder와 관련 클래스 일부를 아래 표시했다.




최초 상태는 S20PipeConfirmBuilder인데, 이는 EocsTransitionBuilder의 정적 메서드인 receive()가 생성한다.


EocsTransitionsBuilder.receive() // S20PipeConfirmBuilder 리턴


AbstractTransitionBuilder를 상속받은 각 클래스는 특정 상태에서 가능한 전이만을 제공한다. 예를 들어, S30PipeConfirmBuilder는 상태 값이 "30"인 배관유무확인2 상태에서 선택할 수 있는 전이 네 개(pipeYes, pipeNo, pipeSelf, pipeTest)를 제공한다. 비슷하게 S30PipeConfirmBeforeAssignBlockBuilder는 한 개의 상태 전이인 assignBlock()만 제공한다.


전이를 위한 메서드는 다음 상태를 위한 Transition Builder를 리턴한다. 각 Transition Builder는 해당 상태에서 가능한 전이만 제공하므로, 잘못된 전이 경로를 설정할 수 없다. 예를 들어, S20PipeConfirmBuilder의 alimBellOfNormalJobgu() 메서드는 S30PipeConfirmBeforeAssignBlockBuilder를 리턴하므로 alimBellOfNormalJobggu() 메서드 호출 다음에는 assignBlock() 메서드만 호출할 수 있다. 다른 메서드는 호출할 수 없다.


EocsTransitions trans = EocsTransitionsBuilder

    .receive() // S20PipeConfirmBuilder 리턴

    .alimBellOfNormalJobgu() // S30PipeConfirmBeforeAssignBlockBuilder 리턴

    .assignBlock(...) // S30PipeConfirmBuilder 리턴

    .pipeYes() // S40DispMeetWaitBuilder 리턴

    .build();



EocsTransitionsBuilder 클래스


public class EocsTransitionsBuilder {


    public static EocsTransitionsBuilder builder() {

        return new EocsTransitionsBuilder();

    }


    private List<Transition> transitions = new ArrayList<>();


    public S20PipeConfirmBuilder receive() {

        addTransition(new Transition(TranstionType.RECEIVE));

        return new S20PipeConfirmBuilder(this);

    }


    public void addTransition(Transition transition) {

        transitions.add(transition);

    }


    public EocsTransitions build() {

        return new EocsTransitions(transitions);

    }

}


EocsTransitionsBuilder 클래스의 receive() 메서드는 TransitionType.RECEIVE을 값으로 갖는 최초 상태 전이를 추가하고, 첫 번째 상태를 위한 S20PipeConfirmBuilder 객체를 리턴한다.


AbstractTransitionBuilder 클래스


AbstractTransitionBuilder은 각 Transition Builder가 필요로 하는 기능을 제공한다.


public static abstract class AbstractTransitionBuilder {

    private EocsTransitionsBuilder builder;


    public AbstractTransitionBuilder(EocsTransitionsBuilder builder) {

        this.builder = builder;

    }


    public final EocsTransitions build() {

        return builder.build();

    }


    protected final void addTransition(Transition transition) {

        builder.addTransition(transition);

    }


    protected final EocsTransitionsBuilder getBuilder() {

        return builder;

    }

}


build() 메서드는 생성자로 전달받은 EocsTransitionsBuilder의 build()를 호출하는데, 이 메서드가 있어 전이 생성 과정에서 언제든지 EocsTransitions를 생성할 수 있다.


개별 Transition Builder 클래스


개별 Transition Builder 클래스는 AbstractTransitionBuilder 클래스를 상속받아 구현했고, 상태 전이를 위한 메서드를 제공했다. 상태 전이 메서드는 addTransition() 메서드를 이용해서 알맞은 상태 전이를 추가하고 다음 Transition Builder를 리턴한다. 다음은 예이다.


public static class S20PipeConfirmBuilder extends AbstractTransitionBuilder {


    public S20PipeConfirmBuilder(EocsTransitionsBuilder builder) {

        super(builder);

    }


    public S30PipeConfirmBeforeAssignBlockBuilder alimBellOfNormalJobgu() {

        addTransition(new Transition(TranstionType.ALIMBELL));

        return new S30PipeConfirmBeforeAssignBlockBuilder(getBuilder());

    }


    public S60EnterWaitBeforeAssignBlockBuilder alimBellOfSmallOrEmergent() {

        addTransition(new Transition(TranstionType.ALIMBELL_OF_SMALL_OR_EMERGENT));

        return new S60EnterWaitBeforeAssignBlockBuilder(getBuilder());

    }

}


최종 상태를 위한 Transition Builder는 더 이상 진행할 전이가 없으므로 다음과 같이 전이를 위한 메서드가 없다.


public static class S90DoneBuilder extends AbstractTransitionBuilder {

    public S90DoneBuilder(EocsTransitionsBuilder builder) {

        super(builder);

    }

}


실행기

실행기는 다음과 같다..


public class EocsTransitionRunner {

    public EocsTransitionRunner(...의존) {

        ...의존주입

    }


    public void run(EocsTransitions trans, String jupno, String empId) {

        trans.transitions().forEach(tran -> {

            runTransition(tran, jupno, empId);

        });

    }


    private void runTransition(Transition tran, String jupno, String employeeId) {

        switch (tran.type()) {

        case RECEIVE:

            initEocs(jupno, DigWorkPathFlag.EOCS_1.cd());

            break;

        case ALIMBELL:

        case ALIMBELL_OF_SMALL_OR_EMERGENT:

            alimBell(jupno, employeeId, "01012345678")

            break;

        case ASSIGN_BLOCK:

            assignBlock(tran, jupno, employeeId);

            break;

        case PIPE_Y:

        ...생략

        case PIPE_T:

            doPipeynProcess(jupno, employeeId, tran);

            break;

        case JOBPR_20:

        case JOBPR_40:

        case JOBPR_50:

            doJobprProcess(jupno, employeeId, tran);

            break;

        case LIPYN_A:

        ...생략

        case LIPYN_N:

            doLipynProcess(jupno, employeeId, tran);

            break;

        case EOCS_POS_DISP:

            unsupport(tran.type());

            break;

        case EOCS_COMPLETE:

            skip(tran.type());

            break;

        }

    }


run() 메서드는 EocsTransitions에 보관된 Transition 목록을 구해서 차례대로 전이를 실행한다. runTransition() 메서드는 각 전이 타입에 따라 알맞은 기능을 실행해서 상태를 알맞게 변경한다. 상태 전이를 수행하는데 필요한 의존은 생성자를 통해서 전달받았다.


이제 데이터를 특정 상태로 맞추고 싶으면 다음과 같은 코드를 사용해서 상태를 변경할 수 있다.


EocsTransitionRunner runner = new EocsTransitionRunner(...);


EocsTransitions data1Trans = EocsTransitionsBuilder.builder() // 전이 정의

        .receive()

        .alimBellOfNormalJobgu()

        .assignBlock(...)

        .pipeYes()

        .build();

runner.run(data1Trans, jupno1, empId); // 전이 실행


EocsTransitions data2Trans = EocsTransitionsBuilder.builder() // 전이 정의

        .receive()

        .build();


runner.run(data2Trans, jupno2, empId); // 전이 실행


각 상태별로 알맞게 전이를 지정할 수 있게 하려고, 상태마다 클래스를 추가했지만, 상태 초기화하는 코드를 보면 쉽게 상태 초기화가 어떤 과정으로 이루어지는지 알 수 있게 되었고, 특정 상태로 맞추는 코드 역시 코드 자동 완성 기능으로 쉽게 정의할 수 있게 되었다.


+ Recent posts