テスティングフレームワークを学ぼう! デイトラJava中級編36 「現場で使われるテスティングフレームワーク(JUnit)とその機能」 

デイトラJava
ハック
ハック

こんにちは!運営者のハックです。

今回は「デイトラ中級編Day36『テスティングフレームワーク(JUnit)』」の学習内容について記録します。

テスティングフレームワークについて学ぼう!

Webアプリケーションの自動テストとは、プログラムが期待通りに動くかどうかを、自動でチェックする方法です。

ねこ奈
ねこ奈

自動ってことはAIとかが自動的にテストしてくれるのにゃ?

それなら楽々テストできるのにゃ!

モナ
モナ

残念ながらすべて自動化されるのではなく、テスト用のプログラムを作成し、そのプログラムを使って繰り返し確認します。

Webアプリケーション開発の世界では自動テストが当たり前

ITにおけるテストは複雑で、しかも何度も行うという都合上、非常に重要なものになります。大量のテストを手動で行うというのは現実的ではないからです。

自動テストを行うことによって新しい機能を追加したり、コードを改善したりするたびに、アプリが正しく動くかを速くて正確に検証できます。

モナ
モナ

講義では単体テスト・結合テストレベルで確認できるような、機能の振る舞いや仕様、画面の動きなどは自動テストを行うと紹介されていました。

逆にユーザビリティテストセキュリティテストなどは自動テストを行うのが困難だそうです。

Javaのテスティングフレームワーク「JUnit」とは?

引用:JUnit5

Javaにおいて自動テストをする仕組みには「JUnit」というものがあります。JUnitはJava言語で書かれたプログラムをチェックするためのツールです。

特に単体テストに強みを持ち、豊富なドキュメントとコミュニティのサポートといった使いやすさと、EclipseやIntelliJといった人気の開発環境との統合が容易な点が優れています。

こうした自動テストのための仕組みやツールを「テスティングフレームワーク」といいます。

モナ
モナ

他のプログラミング言語について調べてみると、PyTest (Python)RSpec (Ruby)、Mocha (JavaScript)というテスティングフレームワークがあるそうです!

実際に自動テストのコードを書いてテストしてみよう!

講義では今まで作ってきたプラグイン機能の一部をJUnitを使い、自動テストコードを書いてみることになりました。

build.gradle内の依存関係にテストの実装をする

まず、build.gradle内のdependencies内に以下のコードを追記しました。


    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.2'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.2'
    testImplementation 'org.mockito:mockito-core:3.+'
モナ
モナ

真ん中の「testRuntimeOnly ‘org.junit.jupiter:junit-jupiter-engine:5.9.2’」はChatGPTからのアドバイスを元に追加した部分です(ひょっとしたらいらないかもですが…)。

追加後、Gradleの変更を読み込みます。

「外部ライブラリ」の中に、左の画像のような「JUnit」に関するプロジェクトライブラリが出来上がっていたら成功です。

次はTestディレクトリを作成し、テストコードの作成を行います。

ここで問題発生!

2023年夏頃からMinecraft Developmentのプラグインの初期化時にテスト用のディレクトリが作成されなくなりました。

参考サイト:Project does not have src/test when initialized

プラグインプロジェクトでテストを実行するために、以下の手順を踏まえました。

①src内にtestディレクトリを作成
②build.gradleに以下の記述を追加

  test {
useJUnitPlatform()
}

③testディレクトリをtest sources rootとしてマーク(testディレクトリを右クリックメニューの下にあります)
④他のクラス内で右クリックメニュー「生成」を選択し、テストを作成する
⑤testディレクトリ内に「EnemyDownCommandTest.java」のテストクラスが生成される

モナ
モナ

特に③の方法は他の方はソースルート指定しなくても問題なかったみたいで、自分の場合Testディレクトリ内にテストクラスが生成されず、対処方法が分からず困っていました。

原因をデイトラメンターの方に質問し、上記の方法を試すことによって解決しました。

ねこ奈
ねこ奈

デイトラメンター陣の方々にはお世話になりっぱなしだにゃ。

モック伊藤さんと仲良くなろう

引用:Mockito framework site
ねこ奈
ねこ奈

…誰!?

モナ
モナ

モック伊藤さんはJava の単体テスト用の素敵なフレームワークです。

モック伊藤さん、改め「mockito(モキート)」はJavaでよく使われるテスト用のフレームワークのひとつです。

このmockitoを使うと、きれいでシンプルなコードでテストを書くことができます。mockitoのテストコードはとても読みやすく、もし間違いがあった時のエラーメッセージも明確でわかりやすいと評判です。

mockitoを使用すると、本物のオブジェクトの代わりに模擬オブジェクト(モック)を作成して、テスト対象のコードとのやりとりをシミュレートできます。これにより、テストを単独で実行し、外部の依存関係から切り離すことができます

モック伊藤さんの使い方

最初の項目で「build.gradle」内のdependencies内にコードを追加したうち、以下のコードを追加していました。

 testImplementation 'org.mockito:mockito-core:3.+'

このとおり、「org.mockito:mockito-core」を依存関係に設定します。

次に「EnemyDownCommandTest.java」テストクラス内にテストコードを記載していきます。
コードは以下のとおりです。

getDifficultyメソッドに対して"easy""normal"を渡し期待される文字列が返るかどうかAssertions.assertEqualsを使って検証しています。

package plugin.enemydouw.command;

import org.bukkit.entity.Player;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import plugin.enemydown.Main;
import plugin.enemydown.command.EnemyDownCommand;

class EnemyDownCommandTest {
    EnemyDownCommand sut;
    @Mock
    Main main;

    @Mock
    Player player;

    @BeforeEach
    void before(){
    sut = new EnemyDownCommand(main);
    }

    @Test
    void difficultyがeasyの時にeasyの文字列が返ること(){
        EnemyDownCommand sut = new EnemyDownCommand(main);
        Player player = Mockito.mock(Player.class);

    String actual = sut.getDifficulty(player, new String[]{"easy"});
        Assertions.assertEquals("easy", actual);
    }

    @Test
    void difficultyがnormalの時にnormalの文字列が返ること(){
        EnemyDownCommand sut = new EnemyDownCommand(main);
        Player player = Mockito.mock(Player.class);

        String actual = sut.getDifficulty(player, new String[]{"normal"});
        Assertions.assertEquals("normal", actual);
    }
}
  • import…JUnitとMockitoの機能を使用するために必要なクラスをインポートしています。
  • sut…「sut」はSubject Under Testの略です。
  • @Mock…Mockitoによって自動的にモック(偽のオブジェクト)を生成することを指示するアノテーションです。
  • @BeforeEach…このアノテーションがついたメソッドは、各テストケースが実行される前に毎回実行されます。
  • @Test…JUnitによってテストメソッドとして認識されるアノテーションです。

ここで重要なのは、Mockを使用することで、EnemyDownCommandクラスが依存するMainやplayerの実際の実装に依存せずにテストができる点です。

これにより、テストが単純で予測可能な状態になり、他のコンポーネントの振る舞いに左右されずにEnemyDownCommandのロジックだけを検証することができます

モナ
モナ

今回はdifficultyがeasyとnormalの2パターンの場合にそれぞれの文字れるが返るテストを行いました。画面のように「2 executed」が表示され、テストが成功したことを確認しました。

まとめ

JUnitには単純なテスト管理以外にも便利な機能があります。

  • @Suiteを使って、複数のテストクラスをグループ化し一括で実行する。
  • @ParameterizedTestを使用して、異なるパラメータで同じテストを複数回実行できる。

ただし、便利な機能があるからといっても、必ず全てを自動テストでやらないといけないわけではないです。

一度だけ確認できれば十分なもの、テストを実行したりテストコードを書くのにかかる時間に対して、実際にテストしたい内容が見合わない、など何をどういう手段でテストするか、という観点を持つことが大事です。
自動テストのコードを書くときには必ず「テストにかかる工数」を意識してください。

ハック
ハック

実務では当然ですが限られた時間の中で開発を行います。

自動コードを書くべきか、作成するテストコードによってどれだけ確認時間が削減できるか、など時間効率を考えながら仕事に取り組めるように、今のうちから考えるクセを付けたいです。

以上で今回の学習記録を終えます。

ここまでご覧いただきありがとうございました。

コメント

タイトルとURLをコピーしました