🔖 오늘 읽은 범위 : 15장, JUnit 들여다보기
JUnit 프레임워크
위 테스트 케이스로 ComparisonCompactor 모듈에 대한 코드 커버리지 분석을 수행했더니 100%가 나왔다. 테스트 케이스가 모든 행, 모든 if 문, 모든 for 문을 실행한다는 의미다. 그래서 나는 모듈이 올바로 동작한다고 자신하게 되었고 모듈 작성자들의 장인정신을 높이 사게 되었다.
디펙터링은 리펙터링의 반대 과정이다. 디펙터링 결과로 나온 코드는 구조적으로 어지럽고 취약하다.
ComparisonCompactor.java(원본)
package junit.framework;
public class ComparisonCompactor {
private static final String ELLIPSIS = " … II ;
private static final String DELTA_END = " ] " ;
private static final String DELTA_START = " [" ;
private int fContextlength ;
private String fExpected;
private String fActual;
private int fPrefix;
private int fSuffix;
public ComparisonCompactor(int contextlength,
String expected,
String actual) {
fContextLength = contextlength;
fExpected = expected;
fActual == actual;
}
public string compact(String message) {
if (fExpected = null || fActual = null || areStringsEqual())
return Assert.format(message, fExpected, fActual) ;
findCommonPrefix( ) ;
findCommonSuffix( ) ;
String expected = compactString(fExpected);
String actual = compactString(fActual);
return Assert.format(message, expected, actual) ;
}
private String compactString(String source) {
String result = DELTA_START + source.substring(fPrefix, source.length() - fSuffix + 1) + DELTA_END;
if (fPrefix > 0)
result = computeCommonPrefix() + result;
if (fSuffix > 0)
result = result + computeCommonSuffix();
return result ;
}
private void findCommonPrefix( ) {
fPrefix = 0;
int end = Math.min(fExpected.length(), fActual.length());
for ( ; fPrefix < end; fPrefix++) {
if (fExpected.charAt(fPrefix) != fActual.charAt(fPrefix))
break;
}
}
private void findCommon5uffix() {
int expectedSuffix = fExpected.length() - 1;
int actualSuffix = fActual.length ( ) - 1 ;
for ( ;
actualSuffix >= fPrefix && expectedSuffix >= fPrefix;
actualSuffix--, expectedSuffix-- ) {
if ( fExpected.charAt(expectedSuffix) != fActual.charAt(actualSuffix)) l
break;
}
fSuffix = fExpected.length() - expectedSuffix;
}
private String computeConmonPrefix( ) {
return (fPrefix > fContextLength ? ELLIPSIS : "") + fExpected.substring(Math.max(0, fPrefix - fContextLength), fPrefix);
}
private String computeCommonSuffix( ) {
int end == Math.min(fExpected.length() - fSuffix + 1 + fContextLength, fExpected.length( )) ;
return fExpected.substring(fExpected.length() - fSuffix + 1, end) +
(fExpected.length( ) - fSuffix + 1 < fExpected.length( ) - fContextLength ? ELLIPSIS : "") ;
}
private boolean areStringsEqual() {
return fExpected.equals(fActual) ;
}
}
참고 링크: junit4/ComparisonCompactor.java at main · junit-team/junit4
비록 저자들이 모듈을 아주 좋은 상태로 남겨두었지만 보이스카우트 규칙에 따르면 우리는 처음 왔을 때보다 더 깨끗하게 해놓고 나가야 한다. 그렇다면 원래 코드를 어떻게 개선하면 좋을까?
findCommonSuffix
를 주의 깊게 살펴보면 숨겨진 시간적인 결합(hidden temporal coupling) 이 존재한다. 다시 말해, findCommandSuffix
는 findCommonPrefix
가 prefixlndex를 계산한다는 사실에 의존한다. 만약 findCommonPrefix
와 findCommonSuffix
를 잘못된 순서로 호출하면 밤샘 디버깅이라는 고생문이 열린다. 그래서 시간 결합을 외부에 노출하고자 findCommonSuffix
를 고쳐 prefixlndex
를 인수로 넘겼다.prefixIndex = findCommonPrefix();
suffixIndex = findCommonSuffix(prefixlndex);