【ABAP】ABAP Unitを使ってみよう(2)　～Test Doubleで依存性を解決する

##はじめに
この記事は、「ABAP Unitを使ってみよう」シリーズの2回目です。
1回目の記事はこちら↓
[【ABAP】ABAP Unitを使ってみよう(1)　～簡単なところから](https://qiita.com/tami/items/374c315ff88249db1b7d)

##自動テストにおける依存性の問題
自動テストでは、テスト対象のソースコードが変わらない限り、実行の都度同じ結果が得られることが求められます。このためには実行環境（たとえば、DBに入っているデータ）によってテストが影響を受けないようにする必要があります。
テスト対象のコードが依存する他のコードやDBなどをdepended-on component(DOC)と呼び、テストではDOCが毎回同じ動きをするようにコントロールする必要があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/1508df8a-ade0-ea84-9188-115c64f8c881.png)
[Managing Dependencies with ABAP Unit](https://help.sap.com/viewer/5371047f1273405bb46725a417f95433/Cloud/en-US/04a2d0fc9cd940db8aedf3fa29e5f07e.html)より引用

##Test Double
Test Doubleとは、テストのときだけDOCに代わって動くコードです。本番用のコードだとDOCが返す結果が変わってしまう場合に、Test Doubleを用意します。あくまでも**テスト対象のコードは本番と同じコード**であり、テスト対象の外部にあるコードを置き換える、というところがポイントです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/3987f5fd-3653-8875-de3b-f488042208c5.png)
[Managing Dependencies with ABAP Unit](https://help.sap.com/viewer/5371047f1273405bb46725a417f95433/Cloud/en-US/04a2d0fc9cd940db8aedf3fa29e5f07e.html)より引用

##Test Doubleの種類
Test Doubleを作成するためにTest Double Frameworkというものが用意されており、以下の種類があります。

| DOCの種類       |Test Double |
|:-----------------|:------------------|
| クラス           |①Test Doubleクラスをマニュアルで作る|
|                  |②ABAP Test Double Framework|
|データベース(CDS) |CDS Test Double Framework|
|データベース(SQL) |ABAP SQL Test Double Framework|
|その他（ソースコード）|ABAP Test Seams|

##【今回のテーマ】ABAP SQL Test Double Framework
今回の記事では、ABAP SQL Test Double Frameworkを使ってDBへのアクセスをTest Doubleに置き換えてみます。

###概要
IF_OSQL_TEST_ENVIRONMENTというインターフェースがTest Doubleを作成します。テストの要領は以下の通りです。

1. テストクラスの初期処理で、使用するテーブルを宣言してTest Doubleを生成する
2. 各テストの前にテストデータをクリアする（毎回同じ結果になるように）
3. テストデータを挿入する（本物のDBではなく、Test Doubleに）
4. テストが終わったらTest Doubleも終了する

###テストシナリオ
キーを指定してテーブルZPRICEを読み込むメソッドを作成します。テストでは、データが正しく取得できていることを確認します。

ZPRICE
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/14a3a635-06f4-3b76-e566-a8e292351919.png)

###テストコード
テストは[前回](https://qiita.com/tami/items/374c315ff88249db1b7d)の記事と同じテストクラスに書いています。

**クラス定義**
赤枠の変数、およびメソッドを追加しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/01ae67ce-1402-b3df-4f34-a9e4b140eaf1.png)

**class_setup**
このメソッドはテストクラス実行時、最初に1回だけ呼ばれます。


```abap
  METHOD class_setup.

    "使用するテーブルを指定してTest Doubleを生成
    environment = cl_osql_test_environment=>create(
      i_dependency_list = VALUE #( ( 'ZPRICE' ) )
    ).

  ENDMETHOD.
```

**setup**
このメソッドは、各テストメソッドが実行される前に呼ばれます。

```abap
  METHOD setup.
    ...

    "各テスト実行前にTest Doubleをリフレッシュする
    environment->clear_doubles( ).

  ENDMETHOD.
```

**class_teardown**
このメソッドは、テストクラスの終了前に呼ばれます。

```abap
  METHOD class_teardown.
    "Test Doubleを終了する
    environment->destroy( ).
  ENDMETHOD.
```

**get_price**
テーブルZPRICEを読み込むメソッドのテストです。

```abap
  METHOD get_price.
    data: lt_price type standard table of zprice,
          ls_price type zprice,
          ls_price_exp type zprice,
          lv_material type matnr.

    " テストデータを挿入
    ls_price_exp = value #( MATERIAL = 'MAT-001' TEXT = 'Test' PRICE = '100.00' CURRENCY = 'USD' ).
    append ls_price_exp to lt_price.
    environment->insert_test_data( lt_price ).

    " テスト対象のメソッドを実行
    lv_material = 'MAT-001'.
    ls_price = mo_class_under_test->get_price( iv_material = lv_material ).

    cl_abap_unit_assert=>assert_equals(
      EXPORTING
        act                  = ls_price             " 実際の結果
        exp                  = ls_price_exp         " 想定結果
        msg                  = 'get price function not working' " エラーの場合のメッセージ
        quit                 = if_aunit_constants=>quit-test
    ).

  ENDMETHOD.
```

**実行結果**
最初はテスト対象のメソッドが空の状態で実施します。結果は以下の通りエラーになります。ここで気づいたのですが、テストの実施順はメソッドの定義順ではなく、メソッドのアルファベット順になるようです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/31135351-0433-a862-11c6-83f6e5de9e99.png)

###テスト対象のメソッド実装

```abap
  METHOD get_price.

    SELECT SINGLE material, text, price, currency
    FROM zprice
    WHERE material = @iv_material
    INTO CORRESPONDING FIELDS OF @r_result.

  ENDMETHOD.
```

###テスト結果
正常終了になりました。（say_helloのテストを最初に実行するように、メソッド名の先頭にprifixを付けました）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/3d2b04e9-ff93-4de7-9d2d-addeb5802633.png)

##感想
DBがからむテストは、通常はテストデータを手で登録したり、トランザクションを使って登録したりと結構手間だったりします（テストパターンが多いとさらに大変）。Test Doubleを使うと欲しいデータをすぐに用意できるメリットがあると感じました。