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

앞 이야기:

작은 요구 사항 변경이 발생했다. 최초의 계획은 '전날 6시 이후의 변경 내역'을 읽어와 리포지토리에 반영하는 것이었는데, 대화를 진행하면서, 아래와 같이 변경되었다.

  • 나: 팀장님, 말씀하신 것 개발이 완료되어 가는데요, '전날 6시 이후로 변경된 내역을 보관하도록 했어요. 매일 오전 6시에 배치로 돌리면 될 것 같아요.
  • A: 최팀장, 그거 업무 시간 중에 변경되는 증분을 보고 싶은 그런 것도 있어서 24시간은 너무 긴 거 같아. 좀 더 짧았으면 좋겠는데....
  • 나: 그럼 얼마나요?
  • A: 음.. 10~20분 주기로?
  • 나: 아,, 네. 단순히 일단위 백업이 아니라 소스 버전 관리 느낌을 좀 더 보태고 싶다는 말씀이시죠?
  • A: 네 그래요~
요구사항의 변화를 수용하기 위한 첫 작업: 테스트로부터 개발


이 요구사항을 충족하려면 전날 6시가 아니라 마지막으로 백업 받은 이전 시간을 어딘가에 기록했다가 다음에 실행할 때 그 시간을 찾아야 한다. 기록할 장소로 파일을 선택했고, 이를 반영하기 위해 테스트를 수정했다.


public class BackupToolTest {


    private static final String TIMEFILE_PATH = "target/timefile.txt";

    private DbCodeFinder mockDbCodeFinder = mock(DbCodeFinder.class);

    private BackupTool tool = new BackupTool();

    private SvnClient mockSvnClient = mock(SvnClient.class);

    private ArgumentCaptor<Date> captor;

    private List<DbCode> dbCodeList;


    @Before

    public void setUp() {

        tool.setDbCodeFinder(mockDbCodeFinder);

        tool.setSvnClient(mockSvnClient);


        captor = ArgumentCaptor.forClass(Date.class);


        createDbCodeList();

        when(mockDbCodeFinder.findUpdatedDbCodesBetween(any(Date.class), any(Date.class)))

                .thenReturn(dbCodeList);


        deleteTimeFileIfExists();

    }


    private void deleteTimeFileIfExists() {

        File file = new File(TIMEFILE_PATH);

        file.delete();

    }


    @Test

    public void givenTimeFileNotExists_run() throws IOException {

        tool.backup();


        verify(mockDbCodeFinder).findUpdatedDbCodesBetween(captor.capture(), captor.capture());

        verify(mockSvnClient).commit(dbCodeList);


        List<Date> dateValues = captor.getAllValues();

        Date fromDate = dateValues.get(0);

        // 파일이 없는 경우 하루 전달 이후 변경 분을 읽어오는 지 검사

        assertThat(getTimeByMinutes(fromDate), equalTo(getOneDayBefore()));

        // tool.backup() 실행 후, 지정한 파일에 마지막 실행 시간 기록하는 지 검사

        assertTimeFileWritten(dateValues.get(1));

    }


    @Test

    public void givenTimeFileExists_run() throws Exception {

        givenTimeFile();


        tool.backup();


        verify(mockDbCodeFinder).findUpdatedDbCodesBetween(captor.capture(), captor.capture());

        verify(mockSvnClient).commit(dbCodeList);


        List<Date> dateValues = captor.getAllValues();

        // 변경된 쿼리 구할 때 사용할 시작 시간 값을 파일에서 읽어오는 지 검증

        assertThat(DateUtil.formatDate(dateValues.get(0)), equalTo("20131018120130"));

        // tool.backup() 실행 후, 지정한 파일에 마지막 실행 시간 기록하는 지 검사

        assertTimeFileWritten(dateValues.get(1));

    }


    private void givenTimeFile() throws Exception {

        File file = new File(TIMEFILE_PATH);

        FileCopyUtils.copy("20131018120130", new FileWriter(file));

    }


    private void assertTimeFileWritten(Date toTime) throws IOException {

        String fileContents = FileCopyUtils.copyToString(new FileReader(TIMEFILE_PATH));

        assertThat(fileContents, equalTo(DateUtil.formatDate(toTime)));

    }


    ... // 나머지 다른 메서드들


처음부터 위 테스트 코드가 완성된 것은 아니고, 아래의 순서로 위 코드가 만들어졌다.

  • givenTimeFileExists_run() 메서드를 작성
    • 마지막 실행 시간을 기록한 파일이 존재할 경우, DB에서 조회할 때 사용되는 시간 값으로 파일에 기록된 값을 사용하는지 검사
    • 실행 종료 후 마지막 실행 시간을 지정한 파일에 보관하는지 검사
  • givenTimeFileExists_run() 테스트를 통과시키기 위해 BackupTool의 backup() 메서드 구현
  • 기존 테스트 메서드의 이름을 givenTimeFileNotExists_run() 으로 변경
    • 기존 파일이 없는 경우이므로, setUp() 메서드에서 파일 존재시 삭제 처리
    • 파일이 없는 경우 24시간 이전 시간을 조회 시작 시간으로 사용하는 지 검사
    • 실행 종료 후 마지막 실행 시간을 지정한 파일에 보관하는지 검사
  • givenTimeFileNotExists_run() 테스트를 통과시키기 위해 BackupTool의 backup() 메서드 구현
아직 테스트 코드에서 발생하고 있는 중복을 제거하진 않았지만, 이 과정에서 BackupTool의 backup() 메서드가 완성되었다. 아래 코드는 테스트 코드가 통과된 뒤 약간의 리팩토링을 한 BackupTool 클래스의 일부이다.

public class BackupTool {
    private String timefilePath;
    private DbCodeFinder dbCodeFinder;
    private SvnClient svnClient;

    public void backup() {
        Date fromTime = getPreviousLastTime();
        Date toTime = getToTime();
        List<DbCode> dbCodeList = findUpdatedDbCodesAfter(fromTime, toTime);
        commit(dbCodeList);
        writeLastTimeToFile(toTime);
    }

    private Date getPreviousLastTime() {
        File file = new File(timefilePath);
        if (file.exists())
            return getPreviousTimeFromFile(file);
        else
            return getOneDayBeforeTime();
    }

    private Date getPreviousTimeFromFile(File file) {
        try {
            String fileContents = FileCopyUtils.copyToString(new FileReader(file));
            SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss");
            return format.parse(fileContents);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private Date getOneDayBeforeTime() {
        Calendar time = Calendar.getInstance();
        time.add(Calendar.DATE, -1);
        return time.getTime();
    }

    private Date getToTime() {
        return new Date();
    }

    private List<DbCode> findUpdatedDbCodesAfter(Date fromDate, Date toTime) {
        return dbCodeFinder.findUpdatedDbCodesBetween(fromDate, toTime);
    }

    private void commit(List<DbCode> dbCodeList) {
        svnClient.commit(dbCodeList);
    }

    private void writeLastTimeToFile(Date toTime) {
        try {
            File file = new File(timefilePath);
            FileCopyUtils.copy(DateUtil.formatDate(toTime), new FileWriter(file));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void setDbCodeFinder(DbCodeFinder dbCodeFinder) {
        this.dbCodeFinder = dbCodeFinder;
    }

    public void setSvnClient(SvnClient svnClient) {
        this.svnClient = svnClient;
    }

    public void setTimefilePath(String timefilePath) {
        this.timefilePath = timefilePath;
    }

}


이제 남은 작업은 정리하지 못한 코드들을 마저 정리하는 것이다. 이건 어린개발자1에게 숙제로 남겨둘 예정이다.


+ Recent posts