抽象クラスのテストはモックを使うといいかも

## 目次
- [1. TemplateMethod パターン](#1-templatemethod-パターン)
- [2. 環境情報](#2-環境情報)
- [3. 実装](#3-実装)
- [4. テスト](#4-テスト)
- [5. まとめ](#5-まとめ)
- [参考文献](#参考文献)

## 1. TemplateMethod パターン
[Java 100 本ノック](https://github.com/JustSystems/java-100practices)を使わせていただき、Java の実装力を上げるべく取り組んでいます。[73 本目](https://github.com/JustSystems/java-100practices/tree/master/contents/073)では、クラス図を見て実装をする問題で、TemplateMethod パターンを使用していました。これまで抽象クラスを実装したことがなくいい練習になったのですが、テストをどう書くか悩みました。中朝クラスを継承するクラスのテストでまとめてテストすることも可能ですが、今回実装クラスが 2 つあるので同じようなテストを 2 回書くのも気が引けるし、何より保守性に問題を感じます。

抽象クラスをテストする方法として、テストで匿名クラスを実装するという方法があるようですが、具体的なテストに実装を書くのはしっくりこない、かつテストケース毎に抽象メソッドの挙動を変えたいとなると、コードの記述量も多くなりそうです。

そんな課題は Mockito で解決できそう。Spy を使い抽象クラスをインスタンス化することで、実装しているメソッドはそのまま使えます。さらに抽象メソッドの挙動をテストケース毎に簡単に変更可能です。

この記事では抽象クラスのテストに焦点をあてるので、実装の全体が気になる方は、後述する GitHub を見ていただけたらなと思います。

## 2. 環境情報
- Java 17
- JUnit 5: 5.10.1
- [Mockito](https://site.mockito.org/): 5.7.0
- 今回扱うコードはこちらの [GitHub リポジトリ](https://github.com/contour-gara/abstract-class-test) に

## 3. 実装
```java:AbstractCommand.java
package org.contourgara;

import lombok.Getter;

@Getter
public abstract class AbstractCommand<T> implements Command<T> {
    private Status status = Status.NONE;
    private T result = null;
    private Exception exception = null;

    protected abstract T executeInner();

    @Override
    public void execute() {
        try {
            status = Status.EXECUTING;
            result = executeInner();
            status = Status.SUCCESS;
        } catch (Exception e) {
            status = Status.ERROR;
            result = null;
            exception = e;
        }
    }
}
```

この抽象クラスの責務は、`executeInner()` メソッドの実行による状態の変更と、実行結果や発生した例外の変数へのセットなので、それだけをテストするとこのクラスの実装を明確にできるテストが書けると考えました。そうすることで、このクラスを継承するクラスのテストもシンプルになりそうです。

## 4. テスト
```java:
package org.contourgara;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;

class AbstractCommandTest {
    private AutoCloseable closeable;
    
    @Spy
    AbstractCommand<?> abstractCommand;

    @BeforeEach
    void setUp() {
        closeable = MockitoAnnotations.openMocks(this);
    }

    @AfterEach
    void tearDown() throws Exception {
        closeable.close();
    }


    @Test
    void コマンド実行前() {
        // assert
        assertThat(abstractCommand.getStatus()).isEqualTo(Status.NONE);
        assertThat(abstractCommand.getResult()).isNull();
        assertThat(abstractCommand.getException()).isNull();
    }

    @Test
    void コマンド終了後() {
        // setup
        doReturn(1).when(abstractCommand).executeInner();

        // execute
        abstractCommand.execute();

        // assert
        assertThat(abstractCommand.getStatus()).isEqualTo(Status.SUCCESS);
        assertThat(abstractCommand.getResult()).isEqualTo(1);
        assertThat(abstractCommand.getException()).isNull();
    }

    @Test
    void 例外発生時() {
        // setup
        doThrow(new RuntimeException()).when(abstractCommand).executeInner();

        // execute
        abstractCommand.execute();

        // assert
        assertThat(abstractCommand.getStatus()).isEqualTo(Status.ERROR);
        assertThat(abstractCommand.getResult()).isNull();
        assertThat(abstractCommand.getException()).isInstanceOf(RuntimeException.class);
    }
}
```
Spy はアノテーションで定義しました。色々調べてもアノテーションで定義している例はなかなか出てきませんが、シンプルに書けていいと思います。注意点としては、初期化をしてあげる必要があることです。今回はテストケース毎にメソッドの挙動を変更するため、BeforeEach に初期化を書きました。

`executeInner()` の挙動をテストメソッド毎に変更しています。**コマンド終了後**では、1 を返すメソッド、**例外発生時**では、RuntimeException を Throw するようにすることで、この抽象クラスがやることを網羅的にテストしています。`executeInner()` 実行直前の状態のテストはできていませんが......

抽象クラスで具体的な実装に依存しないテストが書けました。

## 5. まとめ
テストクラスでやっていることが明確だと、実装も理解しやすくて気持ちが良いですね。

:::note alert
レガシーコードとは、単にテストのないコードである
(cf. レガシーコード改善ガイド)
:::

## 参考文献
- [Java 100 本ノック](https://github.com/JustSystems/java-100practices)
- [3. TemplateMethod パターン](https://www.techscore.com/tech/DesignPattern/TemplateMethod)
- [Java による抽象クラスと protected メソッドのユニットテスト](https://sikeda107.hatenablog.jp/entry/2019/11/03/033932)
- [Mockito で Abstract クラスの実メソッドを呼び出す方法](http://progettista.jp/blog/?p=353)