ABAP Test Double フレームワークの使用方法


# ABAP Test Double フレームワークの使用方法

ABAPのTest DoubleのフレームワークであるABAP Unitを用いることで、テスト対象（以下CUT）が依存する先のクラス（以下DOC）のUnit test時のふるまいを変えることができます。ただし、依存性注入パターンに基づいたプロダクションコードを前提としています。

## 補足
Test DoubleのDoubleは、影武者という意味です。このエントリでは、依存性注入とはコンストラクタ・インジェクションです。つまり、CUTのクラスのコンストラクタに、引数の型としてDOCのオブジェクトのインタフェースをとります。

## 例

``zcl_greetings``がテスト対象のクラスだとします。一方、 ``zcl_timlo`` は、 ``zcl_greetings`` から呼び出されます。本来、 ``zcl_timlo`` はテストする対象ではありません。しかし、普通にテストしようとすると、 ``zcl_timlo`` もついてきてしまいます。これを、「 ``zcl_greetings`` は、``zcl_timlo`` に依存する」といいます。

```plantuml
class zcl_greetings{
timlo: zcl_timlo
constructor( io_timlo: zcl_timlo )
}
class zcl_timlo

zcl_greetings -> zcl_timlo
```

この2つのクラスにインタフェースを定義します。``zcl_greetings`` に対して ``zif_greetings`` 、 ``zcl_timlo`` に対して ``zif_timlo`` を作成します。

```plantuml
interface zif_greetings as "<b>zif_greetings</b>"
interface zif_timlo as "<b>zif_timlo</b>"
class zcl_greetings{
timlo: zcl_timlo
constructor( io_timlo: zcl_timlo )
} 
class zcl_timlo

zcl_greetings -> zcl_timlo
zcl_greetings ..|> zif_greetings
zcl_timlo ..|> zif_timlo
```

上記は、``zcl_greetings``のクラスの中に、``zcl_timlo`` というクラス名を書いています。お互いのクラス名を使わず、インタフェース名だけを使うように変えてみます。

下図は、インタフェースを示す **(I)** と、クラスを示す **(C)** が交互になります。クラスどうしは線がつながっていません。ABAP のTest Doubleはこの状態を前提としています。

```plantuml
interface zif_greetings
interface zif_timlo
class zcl_greetings{
timlo: <b>zif_timlo</b>
constructor( io_timlo: <b>zif_timlo</b> )
} 
class zcl_timlo

zcl_greetings -> zif_timlo
zcl_greetings ..|> zif_greetings
zcl_timlo ..|> zif_timlo
```

#### 依存性注入

では、``zcl_greetings`` は、``zcl_timlo`` をどうやって使えばいいかというと、本番の場合にはABAPならレポートプログラム(``main``)、テストの場合にはテストクラスの``setup``メソッドか、各テストメソッドに書きます。

```abap:main
    timlo = new zcl_timlo(  ).
    cut = new zcl_greetings( timlo ). " コンストラクタの引数にDOCをセット
```

```abap:test
    timlo ?= cl_abap_testdouble=>create( 'zif_timlo' ).
    cut = new zcl_greetings( timlo ). " コンストラクタの引数にDOCをセット
```


### 例題

午前に"Good morning!"、午後に"Good afternoon!"と返すプログラムを考えます。期待結果が実行した時刻によって変るのですが、テストが午前中にPassし、午後にFailするのでは困ります。

狙いは、Test doubleによって、``zcl_timlo`` のふるまいを変えることで、午前と午後の状態を作ることです。[^1]

``zcl_timlo`` のメソッド ``get_local_time`` は本来``sy-timelo``を返します。これを使う代わりに、フレームワークで作ったインスタンスに代役を務めさせ、固定値を返すようにします。

### プロダクションコード：インタフェース

インタフェースを定義します。

``` abap:zif_timlo
INTERFACE zif_timlo
  PUBLIC.
  METHODS get_local_time
    RETURNING VALUE(r_result) TYPE t.
ENDINTERFACE.
```

### クラスの定義と実装

ローカル時刻を返すクラスを実装します。

```abap:zcl_timlo
CLASS zcl_timlo DEFINITION
  PUBLIC FINAL
  CREATE PUBLIC.

  PUBLIC SECTION.
    INTERFACES zif_timlo.
ENDCLASS.


CLASS zcl_timlo IMPLEMENTATION.
  METHOD zif_timlo~get_local_time.
    r_result = sy-timlo.
  ENDMETHOD.
ENDCLASS.
```

時刻が午前の時には文字列Good morning、午後には文字列Good afternoonを返すクラスおよびメソッドを実装します。引数に ``zif_timlo`` の参照をとるコンストラクタを用いています。コンストラクタ・インジェクションによる依存性注入です。

```abap:zcl_greetings
CLASS zcl_greetings DEFINITION
  PUBLIC FINAL
  CREATE PUBLIC.

  PUBLIC SECTION.
    INTERFACES zif_greetings.

    METHODS constructor
      IMPORTING io_timlo TYPE REF TO zif_timlo.

  PRIVATE SECTION.
    CONSTANTS noon TYPE t VALUE '120000'.
    DATA timlo TYPE REF TO zif_timlo.

ENDCLASS.


CLASS zcl_greetings IMPLEMENTATION.
  METHOD zif_greetings~say_greetings.
    IF timlo->get_local_time( ) < noon.
      r_result = `Good morning!`.
    ELSE.
      r_result = `Good afternoon!`.
    ENDIF.
  ENDMETHOD.

  METHOD constructor.
    timlo = io_timlo.
  ENDMETHOD.
ENDCLASS.
```
なお、``zcl_greetings``についても、インタフェースが定義されています。
```abap:zif_greetings
INTERFACE zif_greetings
  PUBLIC.
  METHODS say_greetings
    RETURNING VALUE(r_result) TYPE string.
ENDINTERFACE.
```

### テスト

テストは、``zcl_greetings`` のローカルクラス``lcl_greetings``として定義します。2種類のテストメソッドがあり、それぞれ午前のケース、午後のケースを表します。
```abap:lcl_greetings
CLASS lcl_greetings DEFINITION FINAL
  FOR TESTING RISK LEVEL HARMLESS DURATION SHORT.

  PRIVATE SECTION.
    METHODS _01_say_morning_greetings   FOR TESTING.
    METHODS _02_say_afternoon_greetings FOR TESTING.

ENDCLASS.
```

各テストメソッド で、``cl_abap_testdouble=>create()`` を呼び出します。これは、DOCである``zcl_timlo`` のかわりに、同じインタフェースのインスタンスが生成をおこないます。ただ、生成されるオブジェクトはObject型なので、``?=``をつかってキャストをします。``=``だと、静的に``zif_timlo``型に割り当てられずエラーとなります。``?=``を使うと、有効化時のチェックを回避できます。

```abap:lcl_greetings->_01_say_morning_greetings
    DATA double_timlo TYPE REF TO zif_timlo.
    double_timlo ?= cl_abap_testdouble=>create( 'zif_timlo' ).
```

``cl_abap_testdouble=>configure_call( double_timlo )`` でreturn値を設定します。どのメソッドが対象かについて、一度メソッドをコールすることでテストフレームワークに知らせます。

```abap:lcl_greetings
  METHOD _01_say_morning_greetings.
    DATA double_timlo TYPE REF TO zif_timlo.
    DATA cut          TYPE REF TO zif_greetings.

    double_timlo ?= cl_abap_testdouble=>create( 'zif_timlo' ).
    cut = NEW zcl_greetings( double_timlo ).
    cl_abap_testdouble=>configure_call( double_timlo )->returning( '11:00:00' ).
    double_timlo->get_local_time( ). " 一度空呼び

    cl_abap_unit_assert=>assert_equals( exp = 'Good morning!' "期待値
                                        act = cut->say_greetings( ) "テストの結果
                                        msg = 'Assert: 01 greeting should be good morning' ).
  ENDMETHOD.
  ```
#### あらためてテスト全体

  ```abap:lcl_greetings
CLASS lcl_greetings DEFINITION FINAL
  FOR TESTING RISK LEVEL HARMLESS DURATION SHORT.

  PRIVATE SECTION.
    METHODS _01_say_morning_greetings   FOR TESTING.
    METHODS _02_say_afternoon_greetings FOR TESTING.

ENDCLASS.

CLASS lcl_greetings IMPLEMENTATION.
  METHOD _01_say_morning_greetings.
    DATA double_timlo TYPE REF TO zif_timlo.
    DATA cut          TYPE REF TO zif_greetings.

    double_timlo ?= cl_abap_testdouble=>create( 'zif_timlo' ).
    cut = NEW zcl_greetings( double_timlo ).
    cl_abap_testdouble=>configure_call( double_timlo )->returning( '11:00:00' ).
    double_timlo->get_local_time( ).

    cl_abap_unit_assert=>assert_equals( exp = 'Good morning!'
                                        act = cut->say_greetings( )
                                        msg = 'Assert: 01 greeting should be good morning' ).
  ENDMETHOD.

  METHOD _02_say_afternoon_greetings.
    DATA double_timlo TYPE REF TO zif_timlo.
    DATA cut          TYPE REF TO zif_greetings.

    double_timlo ?= cl_abap_testdouble=>create( 'zif_timlo' ).
    cut = NEW zcl_greetings(  double_timlo ).
    cl_abap_testdouble=>configure_call( double_timlo )->returning( '13:00:00' ).
    double_timlo->get_local_time( ).
    cl_abap_unit_assert=>assert_equals( exp = 'Good afternoon!'
                                        act = cut->say_greetings( )
                                        msg = 'Assert: 02 greeting should be good afternoon' ).
  ENDMETHOD.
ENDCLASS.
  ```

## まとめ

cl_abap_testdoubleを用いるためには、DOCのインタフェースを使ってソースコード上の依存性を排除したうえで、テストクラスからテストダブルのインスタンスを渡せるような設計にしておく必要があります。このためには、テスト容易性のために、本番コードを変える必要があります。ただ、その前提であれば、ABAP UnitのTest Doubleは、簡易に利用することができる方法だと思います。ローカルクラスなどにテスト用の依存先を実装する方法と組み合わせる方法も理解したうえで、適切に使い分けることが必要だと思います。


[^1]: ABAPの標準でついてくるフレームワークなら、``sy-timlo`` を書き換える仕組みが欲しいところですが、そのようなことはできません。テスト対象クラスのローカルクラスでテストする場合に限定すればTest Seamを使うという手もありますが、ここでは取り扱いません（注：ご指摘ありがとうございます）。