메인 컨텐츠로 가기

developerWorks 이용 약관에 동의하시는 경우 제출을 클릭하십시오. 이용 약관 보기.

developerWorks에 처음 로그인하면 developerWorks프로파일이 생성됩니다.귀하의 프로파일에서 동의하신 내용이 공개되지만 이 사항은 언제든지 변경 가능합니다. 귀하의 성명(숨김으로 체크되어 있어도 표시됩니다)과 디스플레이 이름은 게시한 컨텐츠나 사이트 엑세스시 표시됩니다.

모든 정보가 안전하게 전송되었습니다.

  • 닫기 [x]

처음 developerWorks에 로그인할 때 프로파일이 작성되므로, 이를 위해 디스플레이 이름을 선택해야 합니다. 선택하신 디스플레이 이름은 developerWorks에 게시한 컨텐츠에 표시됩니다.

3글자 이상 31글자 이하의 길이로 사용 가능합니다. dW커뮤니티 내에서는 보안상 이메일주소를 제외한 다른 이름을 지정하셔야 합니다.

developerWorks 이용 약관에 동의하시는 경우 제출을 클릭하십시오. 이용 약관 보기.

모든 정보가 안전하게 전송되었습니다.

  • 닫기 [x]

혁신적인 아키텍처와 창발적 설계: 테스트 주도 설계, Part 2

테스트를 통한 설계 진행 및 개선에 관한 추가 정보

Neal Ford, 소프트웨어 아키텍트, IBM Global Business Services (GBS)
Neal Ford사진
Neal Ford는 글로벌 IT 컨설팅 업체인 ThoughtWorks의 소프트웨어 아키텍트이자 Meme Wrangler이다. 애플리케이션, 교육용 자료, 매거진 기사 및 비디오/DVD 프리젠테이션을 설계 및 개발하며 다양한 기술과 관련된 서적의 저자 또는 편집자이기도 하다. 최근에 출판된 책으로는 The Productive Programmer가 있다. 대규모 엔터프라이즈 애플리케이션의 설계 및 빌드에 많은 관심을 가지고 있는 그는 전세계의 개발자 컨퍼런스에서 국제적으로 인정 받고 있는 연사로도 활동하고 있다. 그의 웹 사이트를 살펴보자.

요약:  테스트는 테스트 주도 개발(TDD)의 부수적 효과일 뿐이며, TDD를 올바로 완료하면 전체적인 코드 설계가 개선됩니다. 혁신적인 아키텍처와 창발적 설계를 다룬 본 기사에서는 어떻게 테스트를 통해 부각되는 관심사에서 창발적 설계를 할 수 있는지 보여주는 확장된 예제를 완성해보겠습니다.

이 연재 자세히 보기

기사 게재일:  2011 년 10 월 17 일
난이도: 중급 원문:  보기 PDF:  A4 and Letter (148KB | 14 pages)Get Adobe® Reader®
페이지뷰:  1424 회
의견:  


본 기사는 TDD를 사용하여 코드 작성 전에 테스트를 작성하는 프로세스를 통해 창발적으로 더 나은 설계를 할 수 있는 방법을 살펴보는 2회 시리즈 기사 중 두 번째 기사다. Part 1에서는 사후 테스트 개발(코드를 작성한 후 테스트 작성)을 사용하는 완전수 찾기 프로그램 버전을 작성했다. 그런 다음, TDD를 사용하는 버전(코드 작성 전에 테스트부터 작성하여 테스트가 코드 설계를 주도하도록 하는 방법)을 작성했다. Part 1 말미에서 완전수 목록 유지에 사용되는 데이터 구조의 종류에 대해 고찰하는 과정에서 코드에 근본적인 결함이 있음을 발견했다. 당시 필자는 본능적으로 ArrayList부터 시작했지만, Set에는 추상이 더 적합하다는 사실을 알아차렸다. 따라서 그 지점부터 논의를 시작하여 테스트를 질적으로 개선하고 완료된 코드에 결함이 없는지 검사할 수 있는 방법에 대한 논의로 전개하겠다.

테스트의 질적 개선

Set의 더 나은 추상을 사용하는 테스트를 목록 1에 나타내었다.


목록 1. 더 나은 Set 추상을 사용하는 단위 테스트

@Test public void add_factors() {
    Set<Integer> expected =
            new HashSet<Integer>(Arrays.asList(1, 2, 3, 6));
    Classifier4 c = new Classifier4(6);
    c.addFactor(2);
    c.addFactor(3);
    assertThat(c.getFactors(), is(expected));
}

이 코드는 필자가 해결하려는 문제 영역에서 가장 중요한 부분 중 하나인 숫자의 인수를 얻는 과정을 테스트한다. 숫자의 인수를 얻는 작동이 해결 과제의 가장 복잡한 부분이라서 오류 발생 가능성이 가장 높으므로, 그 작동을 철저히 테스트하려는 것이다. 하지만, 이 코드에 포함된 new HashSet(Arrays.asList(1, 2, 3, 6));은 좀 서투르게 생성되었다. 최신 IDE 지원을 받았어도, 꼴사나운 코딩이라 할 수밖에 없다. newHas를 입력하고 코드 인사이트 기능에서 코드를 자동 생성하도록 하고, <Int를 입력하고 코드 인사이트 기능에서 코드를 자동 생성하도록 하는 식인데, 지겹기 짝이 없다. 이 작업을 좀 더 쉽게 해보자.

이 시리즈의 정보

시리즈의 목적은 소프트웨어 아키텍처 및 설계와 관련하여 자주 논의되지만 정의를 명확하게 내리기 어려운 개념에 대한 신선한 관점을 제공하는 것이다. Neal Ford는 구체적인 예제를 통해 실제 애자일 개발 환경에서 경험하게 되는 혁신적 아키텍처창발적 설계에 대한 견고한 기초 지식을 제공한다. 중요한 아키텍처 및 설계 결정을 최후의 결정 순간까지 미룸으로써 소프트웨어 프로젝트에 해가 되는 불필요한 복잡성을 방지할 수 있다.

적당한 반복을 허용하는 테스트

Andy Hunt와 Dave Thomas가 공동 저술한 The Pragmatic Programmer(참고자료 참조)에서 제시하는 훌륭한 코드 작성 원칙 중 하나는 DRY(Don't Repeat Yourself) 원칙이다. 이 원칙에서는 반복이 문제점으로 이어지는 때가 많으므로 코드에서 모든 반복을 없애라고 권고한다. 하지만, 단위 테스트에는 DRY가 적용되지 않는다. 단위 테스트에서는 테스트 대상 코드의 미묘한 작동을 테스트할 필요가 종종 있어, 비슷하고 중복된 상황으로 이어지곤 한다. Listing 1에서 예상 결과를 만들어내기 위한 코드 복사/붙여넣기가 그 좋은 예다(new HashSet(Arrays.asList(1, 2, 3, 6))). 한 코드의 다양한 변형을 여러 가지 다른 테스트에서 실행하는 데 편리하기 때문이다.

필자의 TDD 관련 경험칙은 테스트에서 적당한 반복은 허용해야 한다는 점이며, 다만 지나친 반복은 곤란하다. 즉, 테스트에서 약간의 중복은 허용 가능하고 어떤 점에서는 불가피한 측면도 있지만, 보기 딱할 정도로 반복되는 생성을 작성하지 말라는 뜻이다. 그래서 흔히 접하는 이런 작성상의 관용적 패턴을 처리하기 위해 private 도우미 메소드를 제공하도록 테스트를 리팩토링해보겠다. 목록 2를 참조하면 된다.


목록 2. 테스트에서 적당한 반복을 허용하도록 하기 위한 도우미 메소드

private Set<Integer> expectationSetWith(Integer... numbers) {
    return new HashSet<Integer>(Arrays.asList(numbers));
}

목록 3에 나타낸 Listing 1에서 다시 작성한 테스트에서 보는 바와 같이, Listing 2의 코드는 필자가 수행하는 모든 테스트의 인수를 훨씬 더 깔끔하게 정리해준다.


목록 3. 숫자의 인수를 검사하기 위해 다소 반복을 허용하는 테스트
@Test public void factors_for_6() {
    Set<Integer> expected = expectationSetWith(1, 2, 3, 6);
    Classifier4 c = new Classifier4(6);
    c.calculateFactors();
    assertThat(c.getFactors(), is(expected));
}

테스트를 작성한다는 이유만으로 훌륭한 설계 원칙을 내팽개쳐도 좋다는 의미는 아니다. 테스트는 다양한 종류의 코드로 수행하지만, (비록 다르지만) 훌륭한 설계 원칙은 테스트에도 적용된다.

경계 조건

TDD에서는 개발자가 새로운 기능을 개발하기 위한 최초의 테스트를 작성할 때 성공보다는 오히려 실패할 테스트를 작성해보도록 권장한다. 그래야 테스트가 모든 상황에서 우연히 통과하는 바람에 실제로는 아무 것도 테스트하지 못하는 테스트(중복 테스트)를 작성할 가능성을 차단할 수 있기 때문이다. 테스트를 통해 옳다고 생각은 하지만 확신을 가질 만큼 충분히 테스트하지 않은 작동을 확인할 수도 있다. 이런 테스트가 반드시 처음부터 실패할 필요는 없다(테스트가 성공 기준을 통과해야 한다고 생각할 때 실패가 순금일지라도 잠재적 버그를 찾았기 때문에). 테스트에 대한 숙고는 무엇을 테스트할 수 있을지에 대한 고찰로 이어진다.

흔히 무시되는 테스트 케이스 중 경계 조건이라는 것이 있다. 즉, 비정상적인 데이터를 입력할 때 코드에서 이를 어떻게 처리할 것인가 하는 문제이다. getFactors()와 관련된 수많은 테스트를 작성하다 보면 어떤 입력이 합리적이고 어떤 입력이 그렇지 않을지 생각하게 된다.

그래서, 목록 4에 나타낸 흥미로운 경계 조건에 대한 몇 가지 테스트를 추가하겠다.


목록 4. 인수 분해를 위한 경계 조건

@Test public void factors_for_100() {
    Classifier5 c = new Classifier5(100);
    c.calculateFactors();
    assertThat(c.getFactors(),
            is(expectationSetWith(1, 100, 2, 50, 4, 25, 5, 20, 10)));
}

@Test(expected = InvalidNumberException.class)
public void cannot_classify_negative_numbers() {
    new Classifier5(-20);
}

@Test public void factors_for_max_int() {
    Classifier5 c = new Classifier5(Integer.MAX_VALUE);
    c.calculateFactors();
    assertThat(c.getFactors(), is(expectationSetWith(1, 2147483647)));
}   

100이라는 수는 인수가 많이 있으므로 흥미를 가질 만한 수라고 생각했다. 여러 가지 다른 숫자를 테스트하면서 문제 영역에서 음수를 포함하는 것은 이치에 닿지 않음을 깨닫고서, 음수를 제외하는 테스트를 작성했다(실제로는 실패한 후 수정했음). 음수에 대해 고찰하면서 MAX_INT도 생각하게 되었다. 시스템 사용자에게 long 숫자가 필요한 경우 일어나는 일을 내 솔루션에서 고려해야 할까? 내가 처음에 가정했던 바에 따르면, 숫자를 정수로 제한했지만 이것이 유효한 가정인지 확실히 해둘 필요가 있다.

요구사항 수집은 손실이 많은 압축

주변을 둘러보고 그림이나 미술품을 찾아보자. 예컨대, 찾아낸 그림에 200만 픽셀이 있다고 해보자. 그 이미지를 2,000픽셀만으로 압축한다면 어떻게 될까? 그래도 같은 모습으로 보일까? (그 작품이 마크 로스코의 것이라면, 아마 그럴 일은 드물 것이다.) 정보를 제거하는 방법으로 압축하는 것은 손실이 많은 압축 알고리즘이다. 압축 버전을 선택하고 이를 200만 픽셀로 복원하려면 몇 가지 할 일이 있다. 때때로 올바로 추측할 수 있어야 하겠지만, 매번 그럴 필요는 없다.

전통적인 "BDUF(Big Design Up Front: 설계를 완벽하게 완료한 후 프로그램 구현을 시작하는 소프트웨어 개발 방식)" 요구사항 세션은 애플리케이션이 수행해야 할 사항에 대해 손실이 많은 압축이다. 비즈니스 분석가가 앞으로 제기될 모든 질문을 예측할 수는 없으므로, 세부사항을 채우기 위한 정보를 작성하는 것은 개발자의 몫으로 남겨진다. 개발자는 불명예스럽게도 바로 이런 점에 약점이 있어, 요구사항을 정의하는 사람들과 이를 구현하는 사람들 사이에서 큰 탄식을 자아내게 하곤 한다.

민첩성이 뛰어난 프로세스에서는 압축 풀기 알고리즘을 최대한 늦은 시간으로 지연하고 이 알고리즘이 실제로 해야 할 역할에 대한 질문에 대답할 수 있는 사람을 항상 곁에 둠으로써 이런 손실을 완화하려는 노력이 이루어진다. 세부사항이 없는 설계는 불가능하므로, 어떤 방법론을 쓰든 상관없이 수집 및 정의 프로세스에서 불가피하게 제거되는 세부사항을 채우기 위한 실행 가능한 방법을 제시해야 한다.

테스트 경계 조건에 따라 필연적으로 애초의 가정을 다시 검사하게 된다. 솔루션을 코딩할 때는 올바르지 않은 가정을 하기 십상이다. 사실, 그건 기존의 요구사항 수집이 지닌 한 가지 약점이며, 필연적으로 발생하는 구현 문제를 제거하기에 충분한 세부사항을 수집할 수 없다. 요구사항 수집은 손실이 많은 양식의 압축이다.

소프트웨어가 해야 할 일을 정의하는 프로세스로 매우 많은 것이 생략되므로, 이를 완벽히 이해하기 위해 물어야 하는 질문을 확인하는 데 도움이 될 메커니즘을 마련할 필요가 있다. 개발자는 대체로 사업가를 제대로 이해하지 못하기 때문에 그들이 실제로 원하는 바를 막연히 추측하는 것은 위험한 일이다. 테스트를 이용해 경계 조건을 조사하면 정확한 이해에 가장 관건이 되는 질문을 찾는 데 도움이 된다. 알맞은 질문을 찾아내는 일은 훌륭한 설계를 하는 데 있어 큰 비중을 차지한다.

포지티브 및 네거티브 테스트

이 문제점에 대한 탐구를 시작할 때, 필자는 문제점을 여러 개의 서브태스크로 분해했다. 테스트를 작성하면서 분해되는 또 다른 중요한 태스크를 발견했다. 그 전체 목록은 다음과 같다.

  1. 문제가 되는 숫자의 인수가 필요하다.
  2. 어떤 숫자가 대상 숫자의 인수인지 판단해야 한다.
  3. 인수 목록에 인수를 추가할 방법을 결정해야 한다.
  4. 인수의 합을 구해야 한다.
  5. 어떤 숫자가 완전한지 판단해야 한다.

나머지 두 가지 태스크는 인수를 합하고 완전성을 테스트하는 것이다. 이 두 가지 태스크에 대해 놀랄 만한 일은 일어나지 않는다. 목록 5에 마지막 두 가지 테스트가 나와 있다.


목록 5. 완전수에 대한 마지막 두 가지 테스트

@Test public void sum() {
    Classifier5 c = new Classifier5(20);
    c.calculateFactors();
    int expected = 1 + 2 + 4 + 5 + 10 + 20;
    assertThat(c.sumOfFactors(), is(expected));
}

@Test public void perfection() {
    int[] perfectNumbers = 
        new int[] {6, 28, 496, 8128, 33550336};
    for (int number : perfectNumbers)
        assertTrue(classifierFor(number).isPerfect());
}  

위키피디아를 확인하여 처음으로 발견되는 몇몇 완전수를 찾은 후, 사실상 완전수를 찾을 수 있는지 확인하는 테스트를 작성할 수 있다. 그러나 아직 끝난 게 아니다. 포지티브 테스트는 이 작업의 절반에 불과하다. 잘못하여 완전수가 아닌 수를 분류하지 않는지 확인하기 위한 테스트도 필요하다. 그 때문에, 목록 6에 나타낸 것과 같은 네거티브 테스트를 작성한다.


목록 6. 완전수 분류가 올바로 이루어지는지 확인하기 위한 네거티브 테스트

@Test public void test_a_bunch_of_numbers() {
    Set<Integer> expected = new HashSet<Integer>(
            Arrays.asList(PERFECT_NUMS));
    for (int i = 2; i < 33550340; i++) {
        if (expected.contains(i))
            assertTrue(classifierFor(i).isPerfect());
        else
            assertFalse(classifierFor(i).isPerfect());
    }
} 

이 코드를 실행하면 완전수 알고리즘이 올바로 작동하는지 알 수 있지만 속도가 매우 느리다. 목록 7에 나타낸 calculateFactors() 메소드를 살펴보면 그 이유를 짐작할 수 있다.


목록 7. 고지식한 getFactors() 메소드

public void calculateFactors() {
    for (int i = 2; i < _number; i++)
        if (isFactor(i))
            addFactor(i);
} 

Listing 7에 명시된 문제점은 Part 1에서 소개한 사후 테스트 버전의 코드에 있는 것과 동일하다. 즉, 인수를 얻기 위한 코드가 대상 숫자까지 계속 진행된다는 점이다. 목록 8에서 리팩토링한 버전에 표시된 것처럼, 인수를 쌍으로 얻는 방식으로 이 코드를 개선하면 대상 숫자의 제곱근까지만 분석해도 되므로 속도가 더 빨라진다.


목록 8. calculateFactors() 메소드의 성능을 더 높인 리팩토링 버전

public void calculateFactors() {
    for (int i = 2; i < sqrt(_number) + 1; i++)
        if (isFactor(i))
            addFactor(i);
}

public void addFactor(int factor) {
    _factors.add(factor);
    _factors.add(_number / factor);
} 

이는 (Part 1에 있는) 사후 테스트 버전의 코드에서 했던 것과 유사한 리팩토링이지만, 이번에는 두 개의 다른 메소드에 변화가 있다. addFactors() 기능을 자체 메소드로 이미 추상화했기 때문에 여기서는 변경이 더욱 간단하며, 이 버전에서는 Set 추상을 사용하여 서투르게 작성된 테스트를 제거하여 사후 테스트 버전에 중복되는 부분이 나타나지 않게 할 수 있다.

최적화의 주요 원칙은 항상 올바로 가져와서 빠르게 만드는 것이어야 한다. 포괄적인 단위 테스트 세트를 사용하면 작동을 손쉽게 확인하여 뭔가를 깨뜨린 건 아닐까 하는 걱정 없이 최적화로 "what if(만약의 경우 가정)" 게임을 마음껏 즐길 수 있다.

테스트 주도 버전의 완전수 찾기 프로그램 작성을 완료했으며, 전체 클래스는 목록 9에 나타낸 바와 같다.


목록 9. 완전 TDD 버전의 숫자 분류기

public class Classifier6 {
    private Set<Integer> _factors;
    private int _number;

    public Classifier6(int number) {
        if (number < 1)
            throw new InvalidNumberException(
            "Can't classify negative numbers");
        _number = number;
        _factors = new HashSet<Integer>();
        _factors.add(1);
        _factors.add(_number);
    }

    private boolean isFactor(int factor) {
        return _number % factor == 0;
    }

    public Set<Integer> getFactors() {
        return _factors;
    }

    private void calculateFactors() {
        for (int i = 2; i < sqrt(_number) + 1; i++)
            if (isFactor(i))
                addFactor(i);
    }

    private void addFactor(int factor) {
        _factors.add(factor);
        _factors.add(_number / factor);
    }

    private int sumOfFactors() {
        int sum = 0;
        for (int i : _factors)
            sum += i;
        return sum;
    }

    public boolean isPerfect() {
        calculateFactors();
        return sumOfFactors() - _number == _number;
    }
}

구성 가능한 메소드

Part 1에 언급된 테스트 주도 개발 코드의 이점 중 하나는 Kent Beck이 제시한 바와 같은 컴포즈드 메소드 패턴(참고자료 참조)을 기반으로 하는 구성 능력(Composability)이다. 컴포즈드 메소드를 사용하면 응집력 있는 메소드가 많은 소프트웨어를 용이하게 빌드할 수 있다. TDD에서는 테스트 능력을 높이기 위해 기능을 작은 청크로 나누어야 하므로 이런 특징이 더욱 촉진된다. 컴포즈드 메소드는 재사용 가능한 빌딩 블록을 생성하므로 설계에 도움이 된다.

TDD에 의해 주도되는 솔루션에서 메소드의 이름과 수에서 이 점을 확인할 수 있다. 다음은 TDD 완전수 분류기의 최종 버전에 있는 메소드이다.

  • isFactor()
  • getFactors()
  • calculateFactors()
  • addFactor()
  • sumOfFactors()
  • isPerfect()

컴포즈드 메소드의 이점을 보여주는 예제를 소개하겠다. 완전수 찾기 프로그램 TDD를 작성했는데, 회사 내부의 다른 그룹에서 사후 테스트 버전의 완전수 찾기 프로그램(예제는 Part 1에 있음)을 작성했다고 해보자. 그러면 사용자들이 "과잉과 부족도 우리가 판단해야 한다!"면서 맹목적인 공포에 빠져들게 된다. 과잉수에서는 인수의 합이 대상 숫자보다 크고, 부족수에서는 인수의 합이 대상 숫자보다 작다.

모든 논리가 하나의 긴 메소드에 있는 사후 테스트 버전의 경우, 사용자가 전체 솔루션을 다시 작성하여 과잉수, 부족수 및 완전수에 공통적으로 있는 코드를 리팩토링해야 한다. TDD 버전에서는 목록 10에 나타낸 두 개의 메소드만 새로 작성하면 된다.


목록 10. 과잉수와 부족수에 대한 지원

public boolean isAbundant() {
    calculateFactors();
    return sumOfFactors() - _number > _number;
}

public boolean isDeficient() {
    calculateFactors();
    return sumOfFactors() - _number < _number;
}

이 두 가지 메소드에 남겨진 유일한 태스크는 calculateFactors() 메소드를 클래스의 생성자로 리팩토링하는 것이다. (isPerfect() 메소드에서는 아무런 해가 없었지만, 세 개의 모든 메소드에서 중복되므로 리팩토링해야 한다.)

코드를 작은 빌딩 블록으로 작성하면 코드를 더 많이 다시 사용할 수 있으므로, 이를 기본 설계 기준 중 하나로 삼아야 한다. 혁신적인 설계를 위해 테스트를 사용하면 구성 가능한 메소드의 작성이 촉진되므로, 설계 개선에 도움이 된다.


코드 품질 평가

Part 1의 초반부에서 필자는 TDD 버전의 코드가 사후 테스트 버전보다 객관적으로 더 나을 것이라 주장했었다. 그런 주장이 타당함을 보여주는 일화적 증거는 꽤 많이 보여주었지만, 그걸 어떻게 증명해야 할까? 물론, 코드 품질을 순전히 객관적으로 평가할 방법은 없지만, 여러 가지 메트릭으로 그에 대한 특정 차원을 표시할 수는 있다. 그 중 하나가 Thomas McCabe가 코드의 복잡도를 평가하기 위해 고안한 사이클로매틱 복잡도(참고자료 참조)이다. 에지 수에서 노드 수를 뺀 다음 2를 더하면 되므로 수식은 꽤 간단하다. 여기서 에지는 실행 경로를, 노드는 코드 행을 나타낸다. 예를 들어, 목록 11에 있는 코드를 살펴보자.


목록 11. 사이클로매틱 복잡도를 확인하기 위한 간단한 Java 메소드

public void doit() {
    if (c1) {
        f1();
    } else {        
        f2();
    }
    if (c2) {
        f3();
    } else {
        f4();
    }
}

Listing 11에 표시된 메소드를 플로우 차트에 다이어그램으로 나타내면, 그림 1에 표시된 것처럼 손쉽게 에지와 노드의 수를 세어 사이클로매틱 복잡도를 계산할 수 있다. 이 메소드의 사이클로매틱 복잡도는 3(8 - 7 + 2)이다.


그림 1. doit() 메소드의 노드 및 에지 수
사이클로매틱 복잡도

두 가지 버전의 완전수 코드를 평가하기 위해, 필자는 JavaNCSS라는 Java 사이클로매틱 복잡도용 오픈 소스 도구를 사용할 것이다("NCSS"는 "Non-Commenting Source Statements"의 약자로, 이 도구에서 측정함). 다운로드 정보는 참고자료를 참조한다.

사후 테스트 코드에서 JavaNCSS를 실행하면 그림 2에 표시된 결과가 나온다.


그림 2. 사후 테스트 완전수 찾기 프로그램의 사이클로매틱 복잡도
사후 테스트 코드에 대한 JavaNCSS 결과

이 버전에서는 메소드가 하나만 있으며, JavaNCSS는 클래스의 메소드 코드 행 수가 평균 13행이고 사이클로매틱 복잡도는 5.00이라고 보고한다. 이를 그림 3에 표시된 TDD 버전과 비교해보자.


그림 3. TDD 버전의 완전수 찾기 프로그램에 대한 사이클로매틱 복잡도
JavaNCSS 결과

TDD 버전의 코드에는 분명히 훨씬 더 많은 메소드가 포함되며, 메소드당 평균 코드 행 수가 3.56개이고 평균 사이클로매틱 복잡도는 1.56에 불과하다. 이 기준으로 평가해보면 TDD 버전이 사후 테스트 코드보다 3배 이상 간단하다. 이 작은 문제를 해결하는 코드도 이 정도 차이가 나므로, 실로 엄청난 차이라 할 수 있다.


요약

혁신적인 아키텍처와 창발적 설계 시리즈로 연재된 2회의 기사에서, 필자는 코드를 작성하기 전에 테스트를 수행하는 이점에 대해 심도 있게 다루었다. 그 결과, 빌딩 블록으로 재사용 가능성이 더욱 높아지고 추상이 개선된 더욱 간단한 메소드를 작성할 수 있게 되었다. 그리고 무료로 테스트를 실시할 수 있다.

테스트를 해보고 실패하면 수정하는 방식으로 더 나은 설계로 나아갈 수 있다. 훌륭한 설계를 하는 데 있어 결코 방심해서는 안 되는 요인 중 하나가 바로 설계자와 설계자가 지닌 선입견이다. 그릇된 판단으로 잘못된 의사결정을 내리는 일을 스스로 피하기란 어려운 일이다. 그래서 TDD를 습관적으로 사용함으로써, 잘못된 개념을 뒤늦게 알아차리는 것이 아니라 문제점에서 해결책이 떠오르도록 하는 것이 쉽고 효과적인 방법이다.

다음 기사에서는 테스트 문제는 잠시 놔두고 Smalltalk에서 빌어온 두 가지 중요한 패턴인 컴포즈드 메소드와 단일 레벨의 추상 원리에 대해 설명한다.


참고자료

교육

제품 및 기술

  • JavaNCSS: JavaNCSS 소스 평가 도구 모음을 다운로드할 수 있다.

토론

필자소개

Neal Ford사진

Neal Ford는 글로벌 IT 컨설팅 업체인 ThoughtWorks의 소프트웨어 아키텍트이자 Meme Wrangler이다. 애플리케이션, 교육용 자료, 매거진 기사 및 비디오/DVD 프리젠테이션을 설계 및 개발하며 다양한 기술과 관련된 서적의 저자 또는 편집자이기도 하다. 최근에 출판된 책으로는 The Productive Programmer가 있다. 대규모 엔터프라이즈 애플리케이션의 설계 및 빌드에 많은 관심을 가지고 있는 그는 전세계의 개발자 컨퍼런스에서 국제적으로 인정 받고 있는 연사로도 활동하고 있다. 그의 웹 사이트를 살펴보자.

잘못된 도움말 신고

부정사용 신고

감사합니다. 이 항목은 운영자가 관심을 표시했습니다.


잘못된 도움말 신고

부정사용 신고

제출실패 신고. 나중에 다시 실행해주세요.


디벨로퍼웍스 로그인


IBM ID가 필요하세요?
IBM ID를 잊으셨습니까?


비밀번호를 잊으셨습니까?
비밀번호 변경

developerWorks 이용 약관에 동의하시는 경우 제출을 클릭하십시오. 이용 약관.

 


developerWorks에 처음 로그인하면 developerWorks프로파일이 생성됩니다.귀하의 프로파일에서 동의하신 내용이 공개되지만 이 사항은 언제든지 변경 가능합니다. 귀하의 성명(숨김으로 체크되어 있어도 표시됩니다)과 디스플레이 이름은 게시한 컨텐츠나 사이트 엑세스시 표시됩니다.

화면상에 보여지는 닉네임을 정하세요.

처음 developerWorks에 로그인할 때 프로파일이 작성되므로, 이를 위해 디스플레이 이름을 선택해야 합니다. 선택하신 디스플레이 이름은 developerWorks에 게시한 컨텐츠에 표시됩니다.

3글자 이상 31글자 이하의 길이로 사용 가능합니다. dW커뮤니티 내에서는 보안상 이메일주소를 제외한 다른 이름을 지정하셔야 합니다.

3개의 &이나 대쉬를 포함해주시고 31글자내로 제한해주세요.


developerWorks 이용 약관에 동의하시는 경우 제출을 클릭하십시오. 이용 약관.

 


아티클 순위

의견

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=20
Zone=자바
ArticleID=765726
ArticleTitle=혁신적인 아키텍처와 창발적 설계: 테스트 주도 설계, Part 2
publish-date=10172011

태그

Help
검색 필드를 사용하여 My developerWorks 내에서 해당 태그가 사용된 모든 종류의 컨텐츠를 검색하십시오.

태그를 더 많이 보거나 적게 보기 위해 슬라이더 막대를 사용하십시오.

인기 태그는 특정 컨텐츠 존(예를 들어, 자바, 리눅스, WebSphere)의 최고 인기 태그를 보여줍니다.

내 태그는 특정 컨텐츠 존(예를 들어, 자바, 리눅스, WebSphere)의 귀하의 태그를 보여줍니다.

검색 필드를 사용하여 My developerWorks 내에서 해당 태그가 사용된 모든 종류의 컨텐츠를 검색하십시오. 인기 태그는 특정 컨텐츠 존(예를 들어, 자바, 리눅스, WebSphere)의 최고 인기 태그를 보여줍니다. 내 태그는 특정 컨텐츠 존(예를 들어, 자바, 리눅스, WebSphere)의 귀하의 태그를 보여줍니다.