주제

소개 페이지 맨 위

컴포넌트는 입력을 인터페이스에 전송하고 입력을 처리하기 위해 컴포넌트를 대기한 후 결과를 확인하여 테스트됩니다. 처리 과정 중에, 컴포넌트는 아마도 입력을 기타 컴포넌트에게 전송하고 그 결과를 사용하여 기타 컴포넌트를 사용할 것입니다.

다이어그램은 수반되는 텍스트에 설명되어 있습니다.

그림 1: 구현한 컴포넌트 테스트

이러한 기타 컴포넌트가 테스트에 문제점을 유발할 수 있습니다.

  1. 기타 컴포넌트가 아직 구현되지 않았을 수 있습니다.
  2. 기타 컴포넌트에, 테스트가 작동되는 것을 막거나 컴포넌트에 의해 테스트 실패가 발생한다는 것을 발견하는 데 많은 시간을 소비하도록 하는 결함이 있을 수 있습니다.
  3. 기타 컴포넌트가 필요시 테스트 실행을 어렵게 할 수 있습니다. 컴포넌트가 상업용 데이터베이스인 경우 회사에 모든 사람에게 충분한 부동 라이센스가 없을 수 있습니다. 또는 컴포넌트 중 하나가 독립된 실험실에서 스케줄된 시간에만 사용할 수 있는 하드웨어일 수 있습니다.
  4. 기타 컴포넌트가 테스트를 지연시켜서 종종 테스트가 충분히 실행되지 않을 수 있습니다. 예를 들어, 데이터베이스를 초기화하는 데 테스트당 5분이 걸릴 수 있습니다.
  5. 컴포넌트가 일정한 결과를 생성하도록 하는 것이 어려울 수 있습니다. 예를 들어, "디스크 공간 없음" 오류를 처리하기 위해 디스크에 쓰는 각각의 메소드를 원할 수도 있습니다. 메소드가 호출되는 순간에 디스크가 가득 찼음을 어떻게 확인할 수 있습니까?

이러한 문제점을 막기 위해 스텁 컴포넌트(본 객체라고도 함)를 사용하도록 선택할 수 있습니다. 스텁 컴포넌트는 최소한 테스트에 응답하는 동안 컴포넌트가 전송하는 값에 대해서는 실제 컴포넌트와 유사하게 작동합니다. 스텁 컴포넌트는 실제 컴포넌트를 뛰어 넘어 컴포넌트 작동의 대부분 또는 모두를 충실히 모방하려고 하는 다목적의 에뮬레이터일 수 있습니다. 예를 들어, 하드웨어에 대해 소프트웨어 에뮬레이터를 빌드하는 것이 종종 좋은 전략입니다. 소프트웨어 에뮬레이터는 더 느릴 뿐이지 하드웨어와 거의 똑같이 작동합니다. 소프트웨어 에뮬레이터가 더 나은 디버깅을 지원하고 여러 사본을 사용할 수 있으며 하드웨어가 완료되기 전에 사용될 수 있으므로 유용합니다.

다이어그램은 수반되는 텍스트에 설명되어 있습니다.

그림 2: 종속된 컴포넌트를 스텁으로 처리하여 구현한 컴포넌트 테스트

스텁에는 두 가지 단점이 있습니다.

  1. 빌드하는 데 비용이 많이 들 수 있습니다. (이것은 특히 에뮬레이터에 해당되는 경우입니다.) 또한 소프트웨어 자체가 되어 유지보수될 필요가 있습니다.
  2. 오류를 감출 수 있습니다. 예를 들어, 컴포넌트가 삼각의 기능을 사용하지만 라이브러리가 아직 사용 가능하지 않다고 가정해 보십시오. 세 가지 테스트 케이스는 다음과 같은 세 가지 각도의 사인을 필요로 합니다: 10도, 45도 및 90도. 올바른 값을 찾기 위해 계산기를 사용하여 각각 0.173648178, 0.707106781 및 1.0을 리턴하는 사인에 대한 스텁을 구성합니다. 컴포넌트를 사인 함수가 라디안에서 인수를 취하여 -0.544021111, 0.850903525 및 0.893996664를 리턴하는 실제 삼각 함수 라이브러리와 통합할 때까지 모든 것이 좋습니다. 나중에 원하는 것보다 더 많은 노력을 통해 코드에 결함이 있음을 발견하게 됩니다.

스텁 및 소프트웨어 설계 프랙티스 페이지 맨 위

실제 컴포넌트가 아직 사용 가능하지 않아서 스텁을 구성하지 않은 경우, 개발 이후에도 스텁을 보유하고 있어야 합니다. 제품 유지보수 중에 스텁이 지원하는 테스트가 중요할 수도 있습니다. 따라서 스텁은 임시적인 코드보다 표준에 따라 작성되어야 합니다. 스텁이 제품 코드의 표준을 충족하지 않으면(예: 대부분이 자체 테스트 모음을 필요로 하지 않는 경우) 나중에 개발자가 제품 변경의 컴포넌트로서 스텁을 유지보수해야 합니다. 유지보수가 매우 힘들면 스텁이 버려지고 스텁에 대한 투자가 손실됩니다.

특히 스텁이 보유될 때 컴포넌트 설계를 변경합니다. 예를 들어, 컴포넌트가 지속적으로 키/값 쌍을 저장하기 위해 데이터베이스를 사용한다고 가정해 보십시오. 두 가지 설계 시나리오를 고려하십시오.

시나리오 1: 데이터베이스가 일반적인 사용에 대해서만이 아니라 테스트에 대해서도 사용됩니다. 데이터베이스의 존재가 컴포넌트에서 숨겨질 필요가 없습니다. 데이터베이스의 이름으로 데이터베이스를 초기화할 수 있습니다.

    public Component(String databaseURL) {
        try {
            databaseConnection =
                DriverManager.getConnection(databaseURL);
            ...
        } catch (SQLException e) {...}
    }

그리고 값을 읽거나 쓴 각각의 위치가 SQL 문을 구성하는 것을 원하지 않으면, 틀림없이 SQL을 포함하는 몇몇 메소드를 가지고 있을 것입니다. 예를 들어, 값을 필요로 하는 컴포넌트 코드는 다음 컴포넌트 메소드를 호출합니다.

    public String get(String key) {
        try {
            Statement stmt =
              databaseConnection.createStatement();
            ResultSet rs = stmt.executeQuery(
              "SELECT value FROM Table1 WHERE key=" + key);
            ...
        } catch (SQLException e) {...}
    }

시나리오 2: 테스트의 경우 데이터베이스가 스텁으로 대체됩니다. 컴포넌트 코드가 실제 데이터베이스에 대해 실행되든지 스텁에 대해 실행되든지 동일하게 보여야 합니다. 따라서 컴포넌트 코드는 추상적 인터페이스의 메소드를 사용하도록 코드화될 필요가 있습니다.

    interface KeyValuePairs {
        String get(String key);
        void put(String key, String value);
    }

테스트는 해시 테이블과 같은 단순한 것과 함께 KeyValuePairs를 구현합니다.

    class FakeDatabase implements KeyValuePairs  {
        Hashtable table = new Hashtable();
        public String get(String key) {
            return (String) table.get(key);
        }
        public void put(String key, String value) {
            table.put(key, value);
        }
    }

테스트되지 않을 때, 컴포넌트는 KeyValuePairs 인터페이스 호출을 SQL 문으로 변환한 어댑터 객체를 사용합니다.

    class DatabaseAdapter implements KeyValuePairs {
        private Connection databaseConnection;
        public DatabaseAdapter(String databaseURL) {
            try {
                databaseConnection =
                    DriverManager.getConnection(databaseURL);
                ...
            } catch (SQLException e) {...}
        }
        public String get(String key) {
            try {
                Statement stmt = 
                  databaseConnection.createStatement();
                ResultSet rs = stmt.executeQuery(
                  "SELECT value FROM Table1 WHERE key=" + key);
                ...
            } catch (SQLException e) {...}
        }
        public void put(String key, String value) {
            ...
        }
    }

컴포넌트는 테스트 및 기타 클라이언트 모두에 대해 단일 구성자를 가질 수 있습니다. 해당 구성자는 KeyValuePairs를 구현하는 객체를 취합니다. 또는 컴포넌트의 보통 클라이언트가 데이터베이스의 이름으로 전달되도록 요구하는 테스트 전용 인터페이스를 제공할 수도 있습니다.

    class Component {
        public Component(String databaseURL) {
            this.valueStash = new DatabaseAdapter(databaseURL);
        }
        // For testing.
        protected Component(KeyValuePairs valueStash) {
            this.valueStash = valueStash;
        }
    }

따라서 클라이언트 프로그래머의 관점에서 두 가지 설계 시나리오가 동일한 API를 산출하지만 한 가지 시나리오를 쉽게 테스트할 수 있습니다. (일부 테스트는 실제 데이터베이스를 사용하고 일부는 스텁 데이터베이스를 사용한다는 것에 주의하십시오.)

추가 정보 페이지 맨 위

스텁에 관련된 자세한 정보는 다음을 참조하십시오.



Rational Unified Process   2003.06.15