LoginSignup
3
2

More than 1 year has passed since last update.

JUnit でユニットテストを書くときに(個人的に)考えていること

Posted at

最近、JUnit でユニットテストを若手と一緒に書く機会がありました。

JUnit を利用したユニットテストは、JUnit4 のころから書くことはあったので、とくに初めて、というわけではないのですが、若手と「どんな風にユニットテストを書くか」ということ話すなかで、自分の中に一定の方針があることに気がつきました。
一度、それをまとめてみるのも悪くないかな、と考えて、ここに記載しておきます。

以降に記載されているのは「※個人の感想」であって、絶対的な指針でないことを、あらかじめ断っておきます。

ユニットテストを書く動機

えらそうに言うほどユニットテストを書いているわけではないのですが、自分がテストを書きたい・書かなきゃと思う動機としては、以下の様なものがあるようです。

動作が正しいことを確認したい

まあ、これは普通にテストの目的ですね。

自信をもってリリースしたい

これも一般的な動機かもしれないですね。
テストを書いて、通したことで、リリース時の安心感を得たい、と。

リファクタリングしたい

先日、 Java17 がリリースされましたね。
新しもの好きは、古くさい書き方をされているコードを見たら、ガンガン新しい書き方に変えたくなるものです。
レコードとか使ってDTOをシンプルに記述したり、switch式使って分岐を格好良くしたり、したいじゃないですか。
そんなとき、(適切な)テストがあれば、安心してリファクタリングすることができますね。
古いシステムにはテストが無かったりするわけなので、「仕方ない、テスト書くか」ってなります。

クラスの仕様・責務を明確にしたい

複雑な分岐をしている、責務が多いクラス・メソッドがある場合に、その内容を理解するためにテストを書きたくなります。
資料に書き出して整理する、でも良いのですが、ユニットテストとして書き起こせば、仕様も理解できるしテストも実施できるしで一石二鳥です。
既にテストが合ったとしても、新規に書き起こします。すると、既存のテストと比較して、自分のテストで足りなかったところや、逆に既存のテストの穴なんかを見つけることができたりします。

開発時にやっていること

上記の様な動機があって、ユニットテストを書くことになるのですが、テスト対象となるシステムの開発を行っているとき、ユニットテストを書きやすいようにやっていることがありました。
新規にクラスやメソッドを書くとき、私は以下の様なことを意識的・無意識的に実施しているようです。

実装を書きながらテストを書く

テストファーストでテスト駆動開発していくメリットに異論は無いのですが、最初にテストを書いて…というやり方を私はしていません。
最初に小さな実装を書いて、そのテストを書いて、また実装を進めて、実装に併せてテストを書いて…というやり方が多いです。
こうすることで、実装をマメに確認できますし、たまに、テストの穴を見つけることもあります。

テストしたいメソッド・変数は、 package private にする

私の場合、新規にクラスを書くときは大抵スコープ宣言なしで書いて、package private にします。
これは何故かというと、「テストクラスで参照したいから」です。
テストしたいのにテスト出来ない、という事態をあらかじめ回避しています。
もし、システム上 private にする必要があれば、そのとき付ければいいや、ぐらいの心持ちでいます。

メソッドの責務は極小化する

テストが複雑になることは避けたいので、メソッドの責務は極小化してシンプルになるようにしています。
感覚的には、if文による分岐がネストしない程度にしています。

ユニットテスト作成時にやっていること

JUnit5 + Mockito を使う

いまどき JUnit4 使っている人はいないと思いますが、新たにユニットテストを書くのであれば JUnit5 を使います。またMockito も導入します。

JUnit5 を導入するのは nested testsparameterized tests が使いたいからです。
Mockito は、依存先クラスに併せてテストケースを作るのが苦痛なので、モックオブジェクトで依存関係を意識せずにテストできるようにするために導入します。

SpringBoot なら spring-boot-starter-test に JUnit5 / Mockito が含まれているので、導入は楽ちんですね。

nested tests で状態を表現する

nested test が使いたいので JUnit5 を導入する、という話をしましたが、私は、nested tests を使ってテストケースの状態を表現して書きます。
以下の様な感じです。

class HogeTest {

    Hoge sut = new Hoge();

    @DisplayName("Hoge の fuga が A のとき")
    @Nested
    class OnFugaIsA {
        @BeforeEach
        void setup() {
            sut.fuga = "A";
        }

        @DisplayName("execute()がtrueを返すこと")
        @Test
        void executeReturnTrue() {
            assertTrue(sut.execute());
        }
    }

    @DisplayName("Hoge の fuga が B のとき")
    @Nested
    class OnFugaIsB {
        @BeforeEach
        void setup() {
            sut.fuga = "B";
        }

        @DisplayName("execute()がfalseを返すこと")
        @Test
        void executeReturnTrue() {
            assertFalse(sut.execute());
        }
    }
}

こう書くことで、ネストしたテストケースでも見通しが良くなります。
また、DisplayName を工夫して書くと、テスト結果が

Hoge の fuga が A のとき
  execute()がtrueを返すこと
Hoge の fuga が B のとき
  execute()がfalseを返すこと

と表示されて、結果の見通してもよくなります。

Mockito を使って、テスト対象の責務にだけ注目する

複雑な構造になると、あるメソッドが別のメソッドを呼び出していたりします。
こうしたメソッドのテストを真面目にやろうとすると、別のメソッドのことも考えてテストを書かなければならなくなりますが、さすがにそれではテストを書くのに苦労するので、Mockitoを使います。
以下の様な感じです。

class methodTest {
    @Spy
    Hoge sut = new Hoge();

    @DisplayTest("execute() は、fuga()を引数 A で呼び出していること")
    @Test
    void executeCallFugaWithA() {
        sut.execute();
        verify(sut).fuga("A");
    }
}

こうすることで、Hoge#execute() の責務である、メソッド fuga() を引数 A で呼び出すことだけに注目してテストできます。
もちろん、メソッド fuga() の責務は、別のテストメソッドでテストすることになります。

さいごに

ざっと、私がJUnitでユニットテストを書くときに考えていることを記載してみました。
今回、ユニットテストについて若手と話す機会を得ることで、自分がなんとなくやっていたユニットテストの書き方について整理ができたと感じています。いろいろと質問をしてくれた若手の方には感謝しかありません。

今回記載事は、もしかして、当たり前のことかもしれませんし、世間の常識とはまるで違っているかも知れません。
異論は受け付けますので、コメントを頂ければ幸いです。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2