연습 4-1(http://javacan.tistory.com/220)에 이어 계속해서 Job 부분을 만들어 나가자.
새로운 모델의 출현: MediaSourceFile
Job은 원본 미디어 파일 정보를 갖고 있어야 한다. 미디어 원본 파일을 MediaSourceFile로 추상화하고, 아래와 같이 이 타입을 필드로 갖도록 코드를 만들었다.
public class Job {
...
private MediaSourceFile mediaSourceFile;
...
public Job(Long id, MediaSourceFile mediaSourceFile) {
this.id = id;
this.mediaSourceFile = mediaSourceFile;
}
...
}
빨간 줄이 나오니까 컴파일 에러가 나지 않도록 변경해 주자. 먼저 MediaSourceFile을 생성할 것이다. 아직 MediaSourceFile의 정확한 구현을 알 수 없으므로 일단 인터페이스로 만든다. 인터페이스만 있고 메서드는 없다.
// 아직 기능 없음
public interface MediaSourceFile {
}
Job 클래스의 컴파일 에러는 사라졌는데, 대신 TranscodingServiceImplTest 코드에서 컴파일 에러가 발생한다.
@RunWith(MockitoJUnitRunner.class)
public class TranscodingServiceImplTest {
private Long jobId = new Long(1);
private Job mockJob = new Job(jobId);
Job의 생성자가 변경되어서 컴파일 에러가 발생하고 있다. Job 객체를 생성하려면 MediaSourceFile 구현체가 필요하다. 일단, MediaSourceFile에 대한 Mock 객체를 만들고, 그 Mock 객체를 받도록 변경한다. Job 객체를 생성하는 위치는 setup() 메서드로 이동시킨다.
@RunWith(MockitoJUnitRunner.class)
public class TranscodingServiceImplTest {
private Long jobId = new Long(1);
@Mock
private MediaSourceFile mediaSourceFile;
private Job mockJob;
...
@Before
public void setup() {
mockJob = new Job(jobId, mediaSourceFile);
...
뭔가 바꾸면 해야 할 게 있다. 그렇다, 바로 테스트 실행이다. 테스트 실행 ... 녹색바! 통과다.
MediaSourceFile은 미디어 파일을 복사하는 것과 관련되었으므로, Job 클래스의 copyMultimediaSourceToLocal() 메서드를 보자.
public class Job {
private Long id;
private MediaSourceFile mediaSourceFile;
...
public Job(Long id, MediaSourceFile mediaSourceFile) {
this.id = id;
this.mediaSourceFile = mediaSourceFile;
}
public void transcode(MediaSourceCopier mediaSourceCopier,
Transcoder transcoder, ThumbnailExtractor thumbnailExtractor,
CreatedFileSaver createdFileSaver,
JobResultNotifier jobResultNotifier) {
try {
changeState(Job.State.MEDIASOURCECOPYING);
File multimediaFile = copyMultimediaSourceToLocal(mediaSourceCopier);
...
} catch (RuntimeException ex) {
exceptionOccurred(ex);
throw ex;
}
}
private File copyMultimediaSourceToLocal(MediaSourceCopier mediaSourceCopier) {
return mediaSourceCopier.copy(id); // mediaSourceFile을 줘야 하나?
}
copyMultimediaSourceToLocal() 메서드는 mediaSourceCopier 객체의 copy() 메서드를 호출할 때 id 필드를 값으로 주고 있다. 읽어올 미디어 원본 파일 정보가 mediaSourceFile 필드에 있으므로, id 대신 mediaSourceFile을 건내주면 될 것 같다.
음... 잠깐! MediaSourceFile에 다음과 같은 메서드를 정의하면 어떨까?
public interface MediaSourceFile {
public File getSourceFile();
}
또는
public interface MediaSourceFile {
public void writeTo(File file);
}
두 가지 중 어떤 것을 선택해도 mediaSourceCopier의 필요성이 사라진다. 오호~ 그렇군. 조금 고민하다가 파일이 어디에 보관될지 여부는 Job이 스스로 결정하는 게 좋을 듯 싶어, getSourceFile() 메서드를 사용하기로 결정했다. 결정했으니 바꿔보자.
- MediaSourceFile 인터페이스에 getSourceFile() 메서드를 추가한다.
- copyMultimediaSourceToLocal() 메서드가 mediaSourceFile.getSourceFile()를 사용하도록 변경한다.
- 이렇게 바꾸면, copyMultimediaSourceToLocal() 메서드가 MediaSourceCopier를 필요로 하지 않는다. 그러니 파라미터에서 mediaSourceCopier를 제거한다.
테스트 실행,,, 녹색! 모두 통과다. 이로서 Job과 관련된 새로운 모델인 MediaSourceFile을 추가하고 MediaSourceFile이 원본으로부터 파일을 가져오도록 설계를 변경하는데 성공했다.
MediaSourceCopier는 이제 필요 없어요!
MediaSourceFile의 등장으로 MediaSourceCopier는 필요 없어졌다. 이제 과감하게 MediaSourceCopier를 소스 코드에서 제거할 차례이다. MediaSourceCopier 타입을 없애고, MediaSourceCopier 타입의 필드, 초기화 코드, 파라미터 등을 모두 제거한다.
// Job 클래스에서 제거
public class Job {
....
public void transcode(MediaSourceCopier mediaSourceCopier,
Transcoder transcoder, ThumbnailExtractor thumbnailExtractor,
CreatedFileSaver createdFileSaver,
JobResultNotifier jobResultNotifier) {
try {
// TranscodingServceImpl 클래스에서 제거
public class TranscodingServiceImpl implements TranscodingService {
private MediaSourceCopier mediaSourceCopier;
private Transcoder transcoder;
...
public TranscodingServiceImpl(MediaSourceCopier mediaSourceCopier,
Transcoder transcoder, ThumbnailExtractor thumbnailExtractor,
CreatedFileSaver createdFileSaver,
JobResultNotifier jobResultNotifier,
JobRepository jobRepository) {
this.mediaSourceCopier = mediaSourceCopier;
this.transcoder = transcoder;
...
}
@Override
public void transcode(Long jobId) {
Job job = jobRepository.findById(jobId);
job.transcode(mediaSourceCopier, transcoder, thumbnailExtractor,
createdFileSaver, jobResultNotifier);
}
// 테스트 코드에서 제거
@RunWith(MockitoJUnitRunner.class)
public class TranscodingServiceImplTest {
...
@Mock
private MediaSourceCopier mediaSourceCopier;
@Mock
private Transcoder transcoder;
...
@Before
public void setup() {
mockJob = new Job(jobId, mediaSourceFile);
when(mediaSourceFile.getSourceFile()).thenReturn(mockMultimediaFile);
transcodingService = new TranscodingServiceImpl(mediaSourceCopier,
transcoder, thumbnailExtractor, createdFileSender,
jobResultNotifier, jobRepository);
when(jobRepository.findById(jobId)).thenReturn(mockJob);
when(mediaSourceCopier.copy(jobId)).thenReturn(mockMultimediaFile);
...
}
코드를 수정했으니까, 다음으로 할 작업은 테스트 실행이다. 테스트 실행~.. 녹색! 모두 통과다.
테스트 코드를 이용한 안정적인 리팩토링
TDD를 이용해서 TranscodingServce의 구현부터 Job의 일부 구현까지 진행되었다. 한 번 정리해 보자.
최초의 TDD 결과물은 아래와 같았다.
하지만, 결과물이 마음에 안 들었고 그래서 Job으로 기능을 옮겼다. 물론, 테스트는 그대로 유지하면서. 그래서 다음과 같이 Job이 출현하고 불필요해진 두 개의 타입이 사라졌다.
그리고, 새로운 도메인 모델인 MediaSourceFile을 추가하는 과정에서 일부 로직이 MediaSourceFile로 이동했고, 이 과정에서 MediaSourceCopier 타입이 또 사라졌다.
그리고, 무엇보다도 중요한 건, 테스트 코드를 통해서 이러한 변화 과정을 안정적으로 진행했다는 점이다.