Skip to main content

Javaでユニットテストを書く

著者

Lewis Gavin

feature java dto

2022年11月18日

0 分で読めます

ソフトウェア開発において、テストは欠かせないベストプラクティスです。ユニットテストは、コードが期待どおりに動作し、最適な状態であることを確認するためのさまざまな手法の1つです。開発者は、特定のメソッドなど、アプリケーションコードの個々のコンポーネント(ユニット)を確認するユニットテストを作成できます。コードの各部分に1つ以上のユニットテストを書き、変更のたびに実行することで、コードベースに不具合が入り込んだらすぐに検出するのが目的です。新しいコードの場合、コードを書くのと同時に(あるいはコードを書く前に)テストを追加する必要があります。既存のコードにテストがない場合は、アプリケーションロジックの実行経路をすべて、少なくとも大部分をカバーできるようにテストを追加しましょう。

ユニットテストでは、外部依存をすべて取り除き、特定のコードがさまざまな状況で期待どおりに動作することを確認します。たとえば、メソッドを新しいパラメーターで何度も呼び出し、正しい値が返されるか、例外がスローされるか、特定の処理が実行されるかを確認します。

利用できるユニットテストフレームワークの中でも、多くのJava開発者がJUnitを使用しています。この記事では、JUnit 5をインストールして使用し、Javaコードのユニットテストを書く方法を紹介します。テストの作成にはVSCode統合開発環境(IDE)を使用し、Java 11とMavenで実行します。ただし、EclipseやIntelliJなどのツールも同様に使えるため、同じ手順で進められます。

Javaコードをレビューするためのツールを幅広く知りたい方は、Javaコードレビュー用ツールをご覧ください。また、基礎知識を深めるには、Javaセキュリティに関する記事もご覧ください。

Javaで初めてのユニットテストを書く方法

このセクションでは、JUnitフレームワークをインストールして、ユニットテストを書いていきます。その後、サンプルコードプロジェクトに対してテストを実行します。このプロジェクトにはアプリケーションコードがあらかじめ設定されており、必要なものがすべてそろったテストクラスのテンプレートも用意されています。

まず、テストを書く前に、サンプルコードを確認しましょう。

package com.snyk.unittestdemo;

public class App 
{
    public static void main( String[] args )
    {
        try {
            System.out.println(generateUpdateUserFirstNameStatement("John", "abcdef1234567"));
        }
        catch (Error e){
            e.printStackTrace();
        }
    }

    public static String generateUpdateUserFirstNameStatement(String firstName, String userId) throws Error{
        if(userId.contains(";") || firstName.contains(";")){
            throw new Error("parameters may contain SQL injection");
        }
        return "UPDATE TABLE user SET first_name = '" + firstName + "' WHERE user_id = '" + userId +"';" ;
    }

}

アプリケーションにはmainメソッドがあり、generateUpdateUserFirstNameStatementメソッドを呼び出して出力を表示します。generateUpdateUserFirstNameStatementメソッドの呼び出しでエラーがスローされた場合、アプリケーションは例外をキャッチし、スタックトレースを表示します。

generateUpdateUserFirstNameStatementメソッドは、2つの文字列パラメーターを受け取り、それらを使ってSQLのUPDATE文を生成するシンプルなメソッドです。どちらかのパラメーターにセミコロンが含まれている場合、入力にSQLインジェクションが含まれている可能性があるため、コードはエラーをスローします。

このメソッドはユニットテストに適した例です。2つのシナリオで機能を確認したいからです。まず、通常の文字列を2つパラメーターとして渡したとき、SQL文を含む文字列が結果として返されることを期待します。次に、2つ目のパラメーターにSQLインジェクションを含む文字列を渡したとき、メソッドがエラーをスローすることを期待します。この2つのケースをユニットテストで確認できます。

まず、JUnitライブラリを含むmaven-surefire-pluginなど、いくつかのMavenパッケージをインストールする必要があります。また、テストの実行時にアプリケーションがJUnitに依存することをMavenに伝える必要もあります。

用意されているpom.xmlファイルを開き、21行目の該当セクションに次の依存関係コードを追加しましょう。

<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.11</version>
  <scope>test</scope>
</dependency>

追加したら、このファイルを保存します。

次に、Mavenを使ってパッケージをインストールします。VSCodeで行うには、左下にあるMavenメニューを展開し、プロジェクトを右クリックしてinstallをクリックします。別のIDEを使用している場合は、手順が異なることがあります。

JUnit 4.11のテスト依存関係が記述されたJavaプロジェクトのpom.xmlと、Mavenのコマンドメニューが表示されたVisual Studio Code

IDEでの操作方法がわからない場合は、コマンドラインから依存関係をインストールすることもできます。ターミナルでプロジェクトのルートディレクトリに移動し、次のMavenコマンドを実行して依存関係をインストールしてください。

注意:お使いのマシンにMavenがインストールされていない場合は、このコマンドを実行する前にインストール手順に従ってください。

mvn install -f pom.xml

完了すると、Mavenはpom.xml fileで指定されているプラグインをインストールします。JUnitライブラリもその1つです。設定で<scope>test</scope>を指定してテスト用の依存関係として定義しているため、テストの実行時にMavenはJUnitを使用します。

Javaのユニットテストで失敗がゼロ件となり、Mavenの「BUILD SUCCESS」が緑色で表示されたターミナル出力。

上の出力からわかるように、インストールプロセスの最後にテスト関連の情報が表示されます。ただし、現在はテストが設定されていないため、実行されたテストの数は0です。アプリケーションにユニットテストを2つ追加して、この状態を変えましょう。

まず、Mavenがテスト実行時に使うsrc/test/java/com/snyk/unittestdemo/AppTest.javaファイルを開きます。最初に、必要なJUnitパッケージをインポートします。ファイルの先頭に次の2つのインポートを追加してください。

import org.junit.Test;

import static org.junit.Assert.assertTrue;

1つ目のインポートを使うと、JUnitのテストアノテーションにアクセスでき、メソッドをテストとして定義できます。2つ目は、JUnitが提供するassertTrueメソッドのインポートです。このメソッドを使って、条件がtrueになるかをテストします。trueになればテストは成功し、そうでなければ失敗します。

最初のテストを追加しましょう。2つの通常の文字列を渡したときに、generateUpdateUserFirstNameStatementメソッドが正しい結果を返すか確認します。AppTestクラスに、次のメソッドを追加してください。

@Test
public void shouldGenerateUpdateStatement()
{
    String actual = App.generateUpdateUserFirstNameStatement("John", "abcdef1234567");
    String expected = "UPDATE TABLE user SET first_name = 'John' WHERE user_id = 'abcdef1234567';";
    assertTrue( actual.equals(expected) );
}

@Testアノテーションを使って、このメソッドをテストとして定義しました。次に、インポートしたassertTrueメソッドを使って、generateUpdateUserFirstNameStatementメソッドの出力を確認します。Johnとabcdef1234567という2つの文字列を渡し、関数が期待どおりの文字列を返すかを確認します。メソッドが正しく動作していれば、このテストは成功するはずです。

ファイルを保存し、テストを実行して成功するか確認します。VSCodeでは、Mavenプロジェクトを右クリックしてtestをクリックします。

JavaのJUnit AppTestと、テスト1件の成功およびビルド成功を示すMavenターミナルの出力を表示したVisual Studio Code

ターミナルからこのコマンドを実行することもできます。ターミナルを開き、プロジェクトフォルダーに移動して、次のコマンドを実行してください。

mvn test -f pom.xml

このコマンドは、アプリケーションをビルドしてテストを実行するようMavenに指示します。テストの出力には、テストが1件実行され、失敗がなかったことが表示されるはずです。

Javaの単体テストを1件実行し、失敗・エラー・スキップがすべて0件で、その後に「BUILD SUCCESS」と表示されているターミナル出力。

次に、generateUpdateUserFirstNameStatementメソッドを変更し、関数が返すUPDATE文を少し変えてみましょう。テストを再実行すると、Mavenで失敗がどのように表示されるか確認できます。

Javaの単体テストが失敗したことを示すターミナル出力。AppTest.shouldGenerateUpdateStatementでアサーションが1件失敗し、Mavenのビルドも失敗しています。

出力が変わり、テストが失敗したことが示されます。Results:という見出しの下に、失敗の数と種類が表示されます。この場合、結果にはgenerateUpdateUserFirstNameStatementテストメソッドと、失敗した行の番号(14行目)が示されています。関数の動作を壊した変更は元に戻してください。

assertTrueメソッドとは少し異なる方法で、関数が意図どおりにエラーをスローすることもテストできます。まず、テストクラスに次のテストメソッドを追加してください。

@Test(expected = Error.class)
public void shouldThrowErrorForSQLInjection()
{
    App.generateUpdateUserFirstNameStatement("John", "abcdef1234567; DROP TABLE user;");
}

ここでは@Testアノテーションを使い、期待する値を指定しています。どちらかのパラメーターにSQLインジェクションが含まれている場合にそのように処理するようプログラムしているため、このメソッドがErrorクラスのインスタンスをスローすることを期待します。これでテストを実行し、成功するか確認できます。

Javaのユニットテストが正常に実行されたことを示すターミナル出力。2件のテストが実行され、失敗やエラーはなく、「BUILD SUCCESS」と表示されています。

JUnitを使って、Javaの最初のユニットテストを実装できました。これにより、コードが期待どおりに機能し、正しい結果を生成することを確認できます。そうでなければ、テストが失敗するため問題に気づけます。

悪意のあるSQLインジェクションにさらされるような脆弱性からアプリケーションを守ることは重要です。このようなテストを追加することで、開発者が将来この脆弱性を持ち込むのを防げます。テストはコードへの信頼を高め、後から開発に加わる人たちのためのセーフティネットになります。Javaのセキュリティに関するベストプラクティスについて、詳しくはこちらをご覧ください。

Snyk Codeも、開発者が気づいていないセキュリティ上の悪い慣行や、テストが書かれていないコードの問題を検出できるツールです。SQLインジェクションに対して脆弱なコードや、APIシークレット、個人を特定できる情報などの機密情報がハードコードされたコードも検出します。

テストは、メソッドがどのように動作すべきかを定義する契約です。開発者がgenerateUpdateUserFirstNameStatementメソッドを更新し、意図せず動作を壊してしまった場合(2つ目のテストで試したように)、ユニットテストが失敗し、失敗に関する情報が示されます。この手法は、コア機能が壊れるのを防ぎ、不安定または危険な変更が本番アプリケーションにリリースされるのを防ぐための防御層となります。

まとめ

このチュートリアルで見てきたように、ユニットテストを書くことは、さまざまなシナリオでコードが意図どおりに動作することを確認するためのシンプルな方法です。また、ユニットテストは共同プロジェクトで作業する際の耐障害性を高め、ほかの開発者が誤ってコードを壊したり、脆弱性を持ち込んだりするのを防ぎます。アプリケーションをテストする方法はほかにもありますが、ユニットテストはすべての開発者が使うべき標準的なツールの1つです。

Javaの技術知識をさらに深めるには、次の関連記事をご覧ください。

Capture the Flagを始めよう

オンデマンドのバーチャル入門ワークショップを見て、Capture the Flagの課題の解き方を学びましょう。

続きを読む

Blog

フロンティアモデルは脆弱性を発見した。攻撃者だけがエクスプロイトチェーンを見つけた。

静的解析で欠陥は見つかりましたが、ライブ攻撃テストで侵害につながる連鎖を実証できたのは唯一でした。Evo COS、Claude Security、Claude Code Securityを比較します。

feature insights context
Blog

自律型攻撃はすでに始まっている。防御もそのスピードに追いつかなければならない。

自律型攻撃者によって、防御に使える時間は短くなっています。継続的な検出、修復、検証、予防で、セキュリティチームが攻撃に歩調を合わせる方法をご紹介します。

Blog

AIコーディングエージェントが不適切なアクセス制御を繰り返し実装する理由

AIコーディングエージェントは、コンパイルが通りレビューも通過する一方で、あるテナントのデータを別のテナントに公開してしまう認可ロジックを生成することがあります。不適切なアクセス制御が検出しにくい理由と、その防止策をご紹介します。