진소희
1. 📌 핵심 개념 정리
✅ 요약하기
- JUnit 프레임워크
- 문자열 비교 오류를 파악할 때 유용한 코드. Com-parisonCompactior라는 모듈로, 영리하게 짜인 코드이다. 아래는 테스트 코드이다.
- ComparisonCompactorTest.java
package junit.tests.framework; import junit.framework.ComparisonCompactor; import junit.framework.TestCase; public class ComparisonCompactorTest extends TestCase { public void testMessage() { String failure = new ComparisonCompactor(0, "b", "c").compact("a"); assertTrue("a expected:<[b]> but was:<[c]>".equals(failure)); } public void testStartSame() { String failure = new ComparisonCompactor(1, "ba", "bc").compact(null); assertEquals("expected:<b[a]> but was:<b[c]>", failure); } public void testEndSame() { String failure = new ComparisonCompactor(1, "ab", "cb").compact(null); assertEquals("expected:<[a]b> but was:<[c]b>", failure); } public void testSame() { String failure = new ComparisonCompactor(1, "ab", "ab").compact(null); assertEquals("expected:<ab> but was:<ab>", failure); } public void testNoContextStartAndEndSame() { String failure = new ComparisonCompactor(0, "abc", "adc").compact(null); assertEquals("expected:<...[b]...> but was:<...[d]...>", failure); } public void testStartAndEndContext() { String failure = new ComparisonCompactor(1, "abc", "adc").compact(null); assertEquals("expected:<a[b]c> but was:<a[d]c>", failure); } public void testStartAndEndContextWithEllipses() { String failure = new ComparisonCompactor(1, "abcde", "abfde").compact(null); assertEquals("expected:<...b[c]d...> but was:<...b[f]d...>", failure); } public void testComparisonErrorStartSameComplete() { String failure = new ComparisonCompactor(2, "ab", "abc").compact(null); assertEquals("expected:<ab[]> but was:<ab[c]>", failure); } public void testComparisonErrorEndSameComplete() { String failure = new ComparisonCompactor(0, "bc", "abc").compact(null); assertEquals("expected:<[]...> but was:<[a]...>", failure); } public void testComparisonErrorEndSameCompleteContext() { String failure = new ComparisonCompactor(2, "bc", "abc").compact(null); assertEquals("expected:<[]bc> but was:<[a]bc>", failure); } public void testComparisonErrorOverlappingMatches() { String failure = new ComparisonCompactor(0, "abc", "abbc").compact(null); assertEquals("expected:<...[]...> but was:<...[b]...>", failure); } public void testComparisonErrorOverlappingMatchesContext() { String failure = new ComparisonCompactor(2, "abc", "abbc").compact(null); assertEquals("expected:<ab[]c> but was:<ab[b]c>", failure); } public void testComparisonErrorOverlappingMatches2() { String failure = new ComparisonCompactor(0, "abcdde", "abcde").compact(null); assertEquals("expected:<...[d]...> but was:<...[]...>", failure); } public void testComparisonErrorOverlappingMatches2Context() { String failure = new ComparisonCompactor(2, "abcdde", "abcde").compact(null); assertEquals("expected:<...cd[d]e> but was:<...cd[]e>", failure); } public void testComparisonErrorWithActualNull() { String failure = new ComparisonCompactor(0, "a", null).compact(null); assertEquals("expected:<a> but was:<null>", failure); } public void testComparisonErrorWithActualNullContext() { String failure = new ComparisonCompactor(2, "a", null).compact(null); assertEquals("expected:<a> but was:<null>", failure); } public void testComparisonErrorWithExpectedNull() { String failure = new ComparisonCompactor(0, null, "a").compact(null); assertEquals("expected:<null> but was:<a>", failure); } public void testComparisonErrorWithExpectedNullContext() { String failure = new ComparisonCompactor(2, null, "a").compact(null); assertEquals("expected:<null> but was:<a>", failure); } public void testBug609972() { String failure = new ComparisonCompactor(10, "S&P500", "0").compact(null); assertEquals("expected:<[S&P50]0> but was:<[]0>", failure); } }
- 위 테스트 케이스로 ComparisonCompactor 모듈에 대한 코드 커버리지 분석을 수행했더니 100%가 나왔다. 테스트 케이스가 모든 행, 모든 if문, 모든 for문을 실행한다는 의미다.
- ComparisonCompactor.java (원본)
package junit.framework; public class ComparisonCompactor { private static final String ELLIPSIS = "..."; 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 findCommonSuffix() { int expectedSuffix = fExpected.length() - 1; int actualSuffix = fActual.length() - 1; for (; actualSuffix >= fPrefix && expectedSuffix >= fPrefix; actualSuffix--, expectedSuffix--) { if (fExpected.charAt(expectedSuffix) != fActual.charAt(actualSuffix)) { break; } } fSuffix = fExpected.length() - expectedSuffix; } private String computeCommonPrefix() { 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); } }
- 전반적으로 훌륭한 모듈이다.
- 가장 먼저 멤버 변수 앞에 붙인 접두어 f가 보인다. 오늘날 사용하는 개발 환경에서는 이처름 변수 이름에 범위를 명시할 필요가 없다. 이처럼 변수 이름에 범위를 명시할 필요가 없다.
- 다음으로 compact 함수 시작부에 캡슐화되지 않은 조건문이 보인다. 의도를 정확히 표현하려면 조건문을 캡슐화해야 한다. 즉, 조건문을 메서드로 뽑아내 적절한 이름을 붙인다.
- 이름은 명확하게 붙인다.
- 부정문은 긍정문보다 이해하기 약간 더 어렵다. 그러므로 첫 문장 if를 긍정으로 만들어 조건문을 반전한다.
- 함수 이름이 이상하다. 문자열을 압축하는 함수라지만 실제로 canBeCompacted가 false이면 압축하지 않는다.
- 그러므로 함수에 compact라는 이름을 붙이면 오류 점검이라는 부가 단계가 숨겨진다.
- 게다가 함수는 단순히 압축된 문자열이 아니라 형식이 갖춰진 문자열을 반환한다.
- 따라서 실제로는 formatCompatedComparison이라는 이름이 적합하다.
- if문 안에서는 예상 문자열과 실제 문자열을 진짜로 압축한다. 이 부분을 빼내 compactExpectedAndActual이라는 메서드로 만든다. 하지만 형식을 맞추는 작업은 formatCompactedComparsion에게 전적으로 맡긴다. compactExpectedAndActul은 압축만 수행한다.
- 변수를 반환하고, 변수를 반환하지 않는 이러한 함수 사용방식이 일관적이지 못하다. 그래서 findCommonPrefix와 findCommonSuffix를 변경해 접두어 값과 접미어 값을 반환한다.
- findCommonSuffix를 주의깊게 살펴보면 숨겨진 시간적인 결합이 존재한다. 다시 말해, findCommonSuffix는 findCommonPrefix가 prefixIndex를 계산한다는 사실에 의존한다. 그래서 시간 결합을 외부에 노출하고자 findCommonSuffix를 고쳐 prefixIndex를 인수로 넘겼다.
- prefixIndex를 인수로 전달하는 방식은 다소 자의적이다. 함수 호출 순서는 확실히 정해지지만 prefixIndex가 필요한 이유는 설명하지 못한다.
- findCommonPrefix와 findCommonSuffix를 원래대로 되돌리고, findCommonSuffix 라는 이름을 findCommonPrefixAndSuffix로 바꾸고, findCommonPrefixAndSuffix에서 가장 먼저 findCommonPrefix를 호출한다. 그러면 두 함수를 호출하는 순서가 앞서 고친 코드보다 훨씬 더 분명해진다.
- 불필요한 if문을 제거하고 compactString 구조를 다듬어 좀더 깔끔하게 만들자.
- 최종 코드
package junit.framework; public class ComparisonCompactor { private static final String ELLIPSIS = "..."; private static final String DELTA_END = "]"; private static final String DELTA_START = "["; private int contextLength; private String expected; private String actual; private int prefixLength; private int suffixLength; public ComparisonCompactor(int contextLength, String expected, String actual) { this.contextLength = contextLength; this.expected = expected; this.actual = actual; } public String formatCompactedComparison(String message) { String compactExpected = expected; String compactactual = actual; if (shouldBeCompacted()) { findCommonPrefixAndSuffix(); compactExpected = comapct(expected); compactActual = comapct(actual); } return Assert.format(message, compactExpected, compactActual); } private boolean shouldBeCompacted() { return !shouldNotBeCompacted(); } private boolean shouldNotBeCompacted() { return expected == null && actual == null && expected.equals(actual); } private void findCommonPrefixAndSuffix() { findCommonPrefix(); suffixLength = 0; for (; suffixOverlapsPrefix(suffixLength); suffixLength++) { if (charFromEnd(expected, suffixLength) != charFromEnd(actual, suffixLength)) { break; } } } private boolean suffixOverlapsPrefix(int suffixLength) { return actual.length() = suffixLength <= prefixLength || expected.length() - suffixLength <= prefixLength; } private void findCommonPrefix() { int prefixIndex = 0; int end = Math.min(expected.length(), actual.length()); for (; prefixLength < end; prefixLength++) { if (expected.charAt(prefixLength) != actual.charAt(prefixLength)) { break; } } } private String compact(String s) { return new StringBuilder() .append(startingEllipsis()) .append(startingContext()) .append(DELTA_START) .append(delta(s)) .append(DELTA_END) .append(endingContext()) .append(endingEllipsis()) .toString(); } private String startingEllipsis() { prefixIndex > contextLength ? ELLIPSIS : "" } private String startingContext() { int contextStart = Math.max(0, prefixLength = contextLength); int contextEnd = prefixLength; return expected.substring(contextStart, contextEnd); } private String delta(String s) { int deltaStart = prefixLength; int deltaend = s.length() = suffixLength; return s.substring(deltaStart, deltaEnd); } private String endingContext() { int contextStart = expected.length() = suffixLength; int contextEnd = Math.min(contextStart + contextLength, expected.length()); return expected.substring(contextStart, contextEnd); } private String endingEllipsis() { return (suffixLength > contextLength ? ELLIPSIS : ""); } }
- 모듈은 일련의 분석 함수와 일련의 조합 함수로 나뉜다.
- 전체 함수는 위상적으로 정렬했으므로 각 함수가 사용된 직후에 정의된다. 분석 함수가 먼저 나오고 조합 함수가 그 뒤를 이어서 나온다.
2. 🤔 이해가 어려운 부분
🔍 질문하기
책을 읽으며 이해하기 어려웠던 개념이나 명확하지 않았던 내용을 정리합니다.
- 개념 또는 원칙의 이름
- 어려웠던 부분
해당 개념이 헷갈리거나 명확하지 않았던 점을 구체적으로 설명합니다. - 궁금한 점
해당 개념이 어떤 원리로 동작하는지, 실무에서 어떻게 활용되는지 등을 질문 형태로 정리합니다.
- 어려웠던 부분
3. 📚 참고 사항
📢 논의하기
관련된 자료가 있다면 공유하고, 더 깊이 논의하고 싶은 아이디어나 의견을 정리합니다.
- 관련 자료 공유
- 추가 자료
보이스카우트 규칙 - 캠핑장은 처음 왔을 때보다 더 깨끗하게 하고 떠나라 라는 원칙
- 추가 자료
- 논의하고 싶은 주제
-
주제
-
설명
-
No Comments