반응형

Robolectric 은 실제 Android framework code 를 포함한 런타임 환경을 생성하여 동작합니다.

그렇기에 테스트할 때 실제 기기에서 테스트 하는 것과 같은 테스트가 가능합니다.

 

Robolectric 은 다음과 같은 한계점을 갖고 있습니다.

  1. Native code - Android Native Code 는 Robolectric 환경에서 실행할 수 없습니다.
  2. Out of process calls - Robolectric 환경에는 Android System service 가 없습니다.
  3. Inadequate testing APIs - Android 는 테스팅에 적합한 API 를 갖고 있지 않습니다.

Robolectric 은 이러한 문제를 해결하기 위해 Shadows 를 제공합니다.

 

Shadow 는 Android 클래스들의 동작을 확장하고 변경할 수 있습니다. 안드로이드 클래스가 초기화되면, Robolectric 은 대응되는 shadow class 를 찾습니다. 그리고 그에 맞는 Shadow 객체를 생성하여 연결합니다.

 

Robolectric 은 바이트 코드를 사용하여 Native code 를 대체하는 Fake implementation 을 구현하고, 추가적으로 테스트 할 수 있는 API 를 제공합니다.

 

Shadow Classes

Shadow 클래스는 인자 값이 없는 생성자를 필요로 합니다.

그래야 Robolectric 이 인스턴스를 생성할 수 있습니다. 그리고 그 클래스에 @Implements 어노테이션과 함께 안드로이드의 어떤 클래스를 Shadow 하는 것인지 명시하여 적용해줍니다.

 

예시는 다음과 같습니다.

// TextView에 대한 Shadow 클래스
@Implements(TextView.class)
public class ShadowTextView extends ShadowView {
    // 다음과 같이 선언을 해주던가, 아니면 선언을 하지 않는다.
        // (컴파일러가 기본 생성자를 자동으로 생성하기 때문)
    public ShadowTextView() {
        super();
    }
}

 

 

Shadow Methods

Shadow 메소드는 안드로이드 클래스에서 정의된 메소드와 똑같은 시그니쳐로 선언하고 @Implementation 어노테이션을 적용하여 구현하면 됩니다.

그러면 Robolectric 이 알아서 그 메소드를 호출해줍니다.

 

예를 들어 안드로이드의 ImageView#setImageResource(int resId) 라는 메소드를 Shadowing 해보겠습니다.

@Implements(ImageView.class)
public class ShadowImageView extends ShadowView {

  @Implementation
  protected void setImageResource(int resId) {
    // implementation here.
  }
}

이때 Shadowing 의 대상이 되는 메소드는 private, static, final 등 모두 상관 없이 적용 가능합니다.

 

@Implementation 이 붙은 Shadowing 메소드는 일반적으로 protected 으로 지정해줍니다.

 

한 가지 유의 할 점은 Shadowing 의 대상이 되는 메소드는 Shadow 클래스의 대상이 되는 오리지널 클래스에서 정의된 메소드여야 한다는 것입니다. 예를 들어 View 클래스에서 정의된 setEnabled() 메소드는 ViewGroup 클래스를 Shadow 한 클래스에서 Shadow method 로 사용할 수 없습니다.

 

Shadow constructors

생성자도 Shadow 할 수 있습니다.

이때는 메소드와 똑같이 @Implementation 을 붙여주고, 생성자의 이름은 __constructor__ 으로 선언합니다. 단, 이때도 역시나 Shadowing 하고자 하는 오리지널 생성자와 파라미터를 똑같이 구성해야 합니다.

 

예를 들어 TextView 의 생성자를 Shadow 하는 코드는 아래와 같습니다.

@Implements(TextView.class)
public class ShadowTextView extends ShadowView {
  private Context context;

    @Implementation
  protected void __constructor__(Context context) {
    this.context = context;
  }
}

 

Using a Custom Shadows

이제 Shadow를 만들었으니 사용도 해보겠습니다. Custom Shadow 클래스들은 @Config 어노테이션의 shadows 필드로 지정해야 사용할 수 있습니다.

 

예를 들어 위에서 만든 ShadowTextView 를 사용하기 위해서는 @Config(shadows={ShadowTextView.class}) 라고 선언해주어야 합니다. 보시다시피 array 로 값을 받기 때문에 1개 이상의 Shadow 클래스를 적용할 수 있습니다.

 

만약 하나의 클래스나 메소드가 아닌 패키지 단위로 적용하고 싶다면 robolectric.properties 에 선언하면 됩니다.

 

커스텀 Shadow 클래스를 코드상에서 사용할 때는 Shadows.shadowOf() 메소드가 아닌 Shadow.extract() 메소드를 통해 리턴 받은 값을 적절하게 캐스팅하여 사용해야 합니다.

 

 

반응형
반응형