9.2. Integration Testing

OSGi처럼 제한적인 환경에서는 여러분들이 작성한 클래스의 가시성과 버전관리, 번들이 다른 번들과 제대로 동작하는지를 확인하는 것이 중요하다.

통합 테스트를 편하게 하기위해, 스프링 DM 프로젝트는 테스트 클래스 계층구조를 제공한다(org.springframework.osgi.test.AbstractOsgiTests를 기반으로) . 이걸 사용해서 자동으로 OSGi 환경에서 실행되는 일반적인 JUnit 테스트 케이스를 작성할 수 있다.

보통 스프링 DM 프로젝트가 지원하는 시나리오는 다음과 같다:

  • OSGi 프레임워크 시작하기(Equinox, Knopflerfish, Felix)
  • 테스트에 필요하다고 기술된 번들 설치 및 시작
  • 테스트 케이스를 fly 번들 안으로 패키징하고, manifest 작성하고 OSGi 프레임워크에 해당 번들을 설치하기
  • OSGi 프레임워크 안에서 테스트 케이스 실행하기
  • 프레임워크 종료하기
  • 테스트 결과를 OSGi 밖에 있는 원래 테스트 케이스 객체에 전달하기

주의할 것
테스팅 프레임워크의 목적은 OSGi 통합 테스트를 OSGi 환경 밖에서 테스트 하는 것이다(Ant/Maven 같은 곳에서). 테스팅 프레임워크는 OSGi 번들로 사용하기 위해 만들어 둔 것이 아니다.

다음을 쭉 살펴보면 JUnit 기반 통합 테스트를 작성하는 것이 쉬워질 것이다.

본 챕터의 나머지는 스프링 DM 테스팅 스위트가 제공하는 기능들을 설명한다.

9.2.1. 간단한 OSGi 통합 테스트 작성하기

진단 프레임워크에는 특정 기능을 제공하는 여러 클래스들이 있는데, 여러분이 작성한 대부분의 클래스들은 org.springframework.osgi.test.AbstractConfigurablBundleCreatorTests를 상속하면 된다.

이 클래스를 확장하고 BundleContext 필드를 사용하여 OSGi 플랫폼을 사용해보자.

public class SimpleOsgiTest extends AbstractConfigurableBundleCreatorTests {

public void testOsgiPlatformStarts() throws Exception {
    System.out.println(bundleContext.getProperty(Constants.FRAMEWORK_VENDOR));
    System.out.println(bundleContext.getProperty(Constants.FRAMEWORK_VERSION));
    System.out.println(bundleContext.getProperty(Constants.FRAMEWORK_EXECUTIONENVIRONMENT));
    }
}

간단하게 JUnit 테스트를 실행하듯이 테스트를 실행하면 된다. Equinox 3.2.x 에서 실행하면 다음과 같은 결과를 얻을 수 있다

Eclipse
1.3.0
OSGi/Minimum-1.0,OSGi/Minimum-1.1,JRE-1.1,J2SE-1.2,J2SE-1.3,J2SE-1.4}

보시다시피, 테스팅 프레임워크는 테스트 실행에 필요한 번들들 필요한 번들들(스프링, 스프링 DM, slf4j …)을 자동으로 설치해준다.

9.2.2. 테스트에 필요한 것들 설치하기

스프링 DM jar와 테스트 자체를 제외하면 몇몇 라이브러리나 통합 테스트 대상이 되는 코드에 대한 의존도가 높다.

아파치 커먼스 Lang에 의존하고 있는 다음의 테스트 코드를 살펴보자.

import org.apache.commons.lang.time.DateFormatUtils;
    …
      public void testCommonsLangDateFormat() throws Exception {
        System.out.println(DateFormatUtils.format(new Date(), “HH:mm:ssZZ”));
    }
}

위 코드를 실행하면, 다음과 같은 예외가 발생할 것이다.

java.lang.IllegalStateException: Unable to dynamically start generated unit test bundle
     …
Caused by: org.osgi.framework.BundleException: The bundle could not be resolved.
Reason: Missing Constraint: Import-Package: org.apache.commons.lang.time; version=”0.0.0″
    …
    … 15 more

테스트는 org.apache.commons.lang.time 패키지를 필요로 하지만, 이걸 공개한 번들은 존재하지 않는다. 이 문제를 해결하기 위해 commons-lang 번들을 설치해본자.(2.4 이상의 버전을 설치해야 한다. 그래야 OSGi 라이브러리다.)

설치되어야 할 번들을 getTestBundlesNames 또는 getTestBundles를 사용해서 명시할 수 있다.

기본적으로 테스트 스위트는 로컬 maven2 저장소를 사용하여 구성물을 위치시킨다. 구성물 그룹 아이디, 이름, 버전을 콤마로 구분한 문자열로 나열한 것을 기본 locator가 읽어서 해당 구성물을 찾아서 설치한다. 커스텀 locator를 구현하여 설정할 수 있다. org.springframework.osgi.test.provisioning.ArtifactLocator 인터페이스를 구현한 다음 끼워 맞추면 된다.

통합 테스트를 다음과 같이 수정하여 필요한 번들을 설치하자.

protected String[] getTestBundlesNames() {
     return new String[] { “org.springframework.osgi, cglib-nodep.osgi, 2.1.3-SNAPSHOT”,
         “org.springframework.osgi, jta.osgi, 1.1-SNAPSHOT”,
         “commons-lang, commons-lang, 2.4” };
     };
}

테스트를 다시 실행하면 위의 번들들이 OSGi 플랫폼에 설치된다.

노트

위에서 언급한 구성물들이 여러분의 로컬 메이븐 저장소에 있어야 한다.

9.2.3. Advanced testing framework topics

테스팅 프레임워크는 커스터마이징을 많이 할 수 있다. 이 번 챕터에서 여러분에게 유용한 후킹을 소개하고자 한다. 하지만, 이런 팁들이 테스트 코드의 복잡도를 높일 수도 있다.

9.2.3.1. 테스트 manifest 커스터마이징

자동 생성된 manifest가 테스트에 적당하지 않을 수도 있다. 예를 들어 manifest에 몇몇 새로운 헤더나 필수가 아니라 부가적인 import가 필요할때가 있다.

간단한 경우, 자동 생성된 manifest를 조작할 수 있다. 아래의 예제에서 번들 클래스패스를 명시적으로 설정하고 있다.

protected Manifest getManifest() {
      // let the testing framework create/load the manifest
      Manifest mf = super.getManifest();
      // add Bundle-Classpath:
      mf.getMainAttributes().putValue(Constants.BUNDLE_CLASSPATH, “.,bundleclasspath/simple.jar”);
      return mf;
}

또 다른 방법으로 여러분이 직접 작성한 manifest 파일을 getManifestLocation() 메소드에 설정할 수도 있다.

protected String getManifestLocation() {
      return “classpath:com/xyz/abc/test/MyTestTest.MF”;
}

둘 모두 manifest에 다음과 같이 설정되어 있어야 한다.

“Bundle-Activator: org.springframework.osgi.test.JUnitTestActivator”

저렇게 설정되어 있지 않으면, 테스팅이 제대로 동작하지 않을 것이다. 또한 JUnit, Spring, Spring DM의 특정 패키지들을 import 해줘야 된다.

Import-Package: junit.framework,
  org.osgi.framework,
  org.apache.commons.logging,
  org.springframework.util,
  org.springframework.osgi.service,
  org.springframework.osgi.util,
  org.springframework.osgi.test,
  org.springframework.context

테스트 클래스가 사용할 패키지 import에 실패하면 테스트는 실패할 것이고 NoDefClassFoundError가 발생할 것이다.

9.2.3.2. 테스트 번들 컨텐츠 커스터마이징

기본적으로, 테스트 대상이 되는 번들을 테스트 할 때 테스팅 인프라는 ./target/test-classes 폴더 밑에 있는 모든 클래스, xml, 프로퍼티 파일들을 사용한다. 이건 메이븐 구조에 맞게 설계된거다. 이런 설정을 두 가지 방법으로 변경할 수 있다.

  • AbstractConfigrableBundleCreatorTests getXXX 메소드를 구현하는 프로그래밍을 하는 방법.
  • 테스트 케이스와 비슷한 이름을 가진 프로퍼티 파일을 작성하는 선언적인 방법. 예를 들어, com.xyz.MyTest 가 사용할 설정 프로퍼티 파일은 com/xyz/MyTest-bundle.properties 파일이어야 한다.

표 9.1. 기본 테스트 jar 컨텐츠 설정

생략

이 옵션은 특정 리소스를 필요로 하는 테스트를 작성할 때 유용하다. AbstractConfigurableBundleCreatorTests와 AbstractOnTheFlyBundleCreatorTests를 살펴보면 보다 많은 후킹 메소드를 찾을 수 있을 것이다

9.2.3.3. MANIFEST.MF 생성 이해하기

테스트 번들 컨텐츠를 기반으로 테스트 manifest를 자동 생성 해주는 테스팅 프레임워크의 유용한 기능 중 하나다. 바이트코드를 분석하여 필요로 하는 페키지들을 import 하는 manifest를 작성해준다. 생성된 번들은 테스트를 실행하기 위한 용도기 때문에, 다음과 같은 가정을 전제로 한다.

  • 아무런 패키지도 export하지 않는다.
  • 페키지를 나누는 기능(서로 다른 번들에서 같은 패키지에 클래스들을 제공하는 기능)을 지원하지 않는다. Split package는 OSGi 스펙 3.13에 따라서 권장하지 않는다.
  • 테스트 번들은 오직 테스트 클래스들만 가지고 있다. 기본 설정을 변경하려면, createManifestOnlyFromTestClass 가 true를 반환하도록 재정의하라.

protected boolean createManifestOnlyFromTestClass() {
    return true;
}

노트

번들에 담겨있는 클래스들의 수와 크기에 Manifest를 작성하는데 필요한 시간이 비례한다.

일반적인 OSGi 번들용 manifest를 작성하는 것이 목적이 아니라, 빠르고 간단하게 테스트를 위한 것을 만들기 위한 용도이다.

9.2.4. OSGi application context 만들기

스프링 DM 테스팅 스위트는 스프링 테스팅 클래스들을 기반으로 하고 있다. application context를 만들려면, getConfigLocation 메소드를 재정의하여 application context 설정파일 위치를 알려주면 된다. 실행시에, OSGi application context가 생성되고 테스트 케이스를 실행할 동안 캐쉬될 것이다.

protected String[] getConfigLocations() {
   return new String[] { “/com/xyz/abc/test/MyTestContext.xml” };
}


9.2.5. 사용할 OSGi 플랫폼 설정하기

테스팅 프레임워크는 3개의 OSGi 4.0 구현체, Equinox, Knopflefish, Felix를 지원한다. 이걸 사용하려면 테스트 클래스패스에 이들이 존재해야 한다. 기본적으로 테스팅 프레임워크는 Equinox를 사용한다. 이 설정을 바꾸는 방법은 두 가지다.

  • getPlatformName() 메소드를 사용하는 프로그래밍적인 방법.

Platforms 인터페이스 구현체의 이름을 설정하여 사용할 플랫폼을 알려준다.

protected String getPlatformName() {
   return Platforms.FELIX;
}
  • org.springframework.osgi.test.framework 시스템 속성을 사용한 선언적인 방법

만약 이 속성이 설정되면, 테스팅 프레임워크는 이 값을 플랫폼 구현체의 풀 네임 값으로 사용할 것이다. 만약 문제가 생기면, Equinox를 다시 사용할 꺼고 경고 메시지를 로깅한다. 이 옵션은 (Ant나 Maven같은) 빌드 툴에게 유용하다. 왜냐면 테스트 코드를 변경하지 않고도 특정 타켓 환경을 알려줄 수 있으니까.

9.2.6. 테스트 의존성 대기하기

테스팅 프레임워크에 내장된 기능 중 하나는 테스트를 실행할 때 필요한 의존성들이 설치 될 때까지 기다리는 것이다. OSGi 플랫폼의 동시성 때문에, 번들이 설치됐다고 해서 해당 번들이 제공하는 서비스가 동작중이라는 것을 보장할 수는 없다. 테스트가 필요로 하는 서비스가 완전히 가용하기 전에 테스트를 실행하는 건 테스트 결과를 더럽히는 일이다. 기본적으로 테스팅 프레임워크는 사용자가 설치한 번들을 모두 조사해서, 스프링을 사용한 번들일 경우 해당 번들이 완전히 시작될 때까지(시작 상태가 되면 서비스 설치된거니까..) 기다린다. 이 기본 행위를 shouldWaitForSpringBundlesContextCreation 메소드에서 변경할 수 있다. AbstractSynchronizedOsgiTests를 참조하라.

9.2.7. 테스팅 프레임워크 성능

테스팅 프레임워크가 제공하는 모든 기능을 고려했을 떄, 성능적인 측면에서 병목현상을 야기하지는 않을지 의심스러울 것이다. 먼저, 테스팅 프레임워크가 자동으로 해주는 모든 일들은 어떻게 해서든 해야만 하는 일들이다. 그 작업들을 손수하기에는 에러가 발생할 여지가 너무 많고 시간 소비도 만만치 않다.

현재의 인프라는 거의 반년간 사용해 왔다. 통합 테스트(120개 정도)를 실행하는데 노트북에서 3분 30초 가량 걸린다. 대부분의 시간은 OSGi 플랫폼을 시작하고 끄는데 소비된다. “테스팅 프레임워크”는 오직 10%가량의 시간만 소비한다.

하지만 우린 필요한 설정을 보다 최소화하고, 보다 더 빠르고 똑똑하게 만들려고 한다. 좋은 아이디어가 있으면 이슈트래커나 포럼을 통해서 언제든지 제안해달라. 

9.1. OSGi Mocks

대부분의 OSGi API들이 인터페이스고 EasyMock같은 라이브러리를 사용하여 목객체를 만들어 사용하는 것이 간단할 수도 있지만, 실제로는 상당히 많은 코드를 필요로 한다.(특히 JDK 1.4에서..) 테스트를 짧고 간결하게 유지하기위해, 스프링 DM은 org.springframework.osgi.mock 패키지 아래에 OSGi 목들을 제공한다.

이것들이 유용한지 아닌지는 여러분들에게 달려있다. 우린 스프링 DM 테스트 스위트를 만들 때 이들을 상당히 많이 사용했다. 아래의 코드는 그런 테스트 코드에서 흔히 볼 수 있는 코드 조각이다.

private ServiceReference reference;
private BundleContext bundleContext;
private Object service;
   
protected void setUp() throws Exception {
    reference = new MockServiceReference();
    bundleContext = new MockBundleContext() {

        public ServiceReference getServiceReference(String clazz) {
            return reference;
        }

        public ServiceReference[] getServiceReferences(String clazz, String filter)
                throws InvalidSyntaxException {
            return new ServiceReference[] { reference };
        }
       
        public Object getService(ServiceReference ref) {
            if (reference == ref)
               return service;
            super.getService(ref);
        }
    };

    …
}
   
public void testComponent() throws Exception {
    OsgiComponent comp = new OsgiComponent(bundleContext);
   
    assertSame(reference, comp.getReference());
    assertSame(object, comp.getTarget());
}

마무리 하자면, 스프링 DM이 제공하는 라이브러리들을 사용해보고 여러분들이 가장 편한다고 느끼는 라이브러리를 사용하라. 우리가 작성한 테스트 스위트에서는 EasyMock 라이브러리와 통합 테스트를 많이 사용했다.

Chapter 9. Testing OSGi based Applications

베스트 프랙티스를 잘 따르고 스프링 DM 지원 기능을 사용한다면, 여러분들이 작성한 bean 클래스들은 단위 테스트가 용이할 것이다. OSGi를 직접 참조하지도 않을 것이고, 최소한의 인터페이스 기반의 OSGi API(BundleContext와 같은..)만을 사용하여 mock 객체를 만드는 것도 쉬워질 것이다. 단위 테스트와 통합 테스트 둘 다 스프링 DM이 지원한다.