i-Vinci TechBlog
株式会社i-Vinciの技術ブログ

自動テストの基礎と実践: Python、TypeScript、Javaで始めるユニットテスト

入社2年目、実務経験浅めのSです。

案件にアサインされてから、TypeScript、Java、Pythonと次々と未経験の言語に取り組んでおり、さらにテストフレームワークも分からず初めてのことばかりで、てんやわんやの日々を送っています。

今回は、ブログの執筆担当が回ってきたことを機に、pytest(Python)、Jest(TypeScript)、JUnit(Java)といった自動ユニットテストの基本を整理してみたいと思います。

自動テストとは?

自動テストは、ソフトウェアの品質を確保するためにコードのテストを自動化する手法です。
手動テストの代わりにテストスクリプトを使用し、自動でテストを実行します。
これにより、テストが迅速に行え、バグや不具合を早期に発見することが可能です。

自動テストの基本概念

自動テストは、ソフトウェアのコードや機能が期待通りに動作するかを検証するために利用されます。
手動テストの代わりに自動化ツールを使ってテストを実行し、その結果を検証します。
これにより、テスト実行のスピードが向上し、開発中のバグや不具合を早期に発見できます。

テストの種類

ユニットテスト(単体テスト)

目的: コードの最小単位(ユニット)が正しく動作するか確認します。
例: 数値の足し算をテストする。

統合テスト

目的: 複数のコンポーネントが連携して正しく動作するか確認します。
例: APIエンドポイントが正しくデータを返すかテストする。

エンドツーエンドテスト(E2Eテスト)

目的: アプリケーション全体をユーザーの視点でテストします。
例: ユーザーがログインし、データ入力から結果確認までのフローをテストする。

各言語でのユニットテストの実装方法

ユニットテストは、ソフトウェア開発における基本的なテスト手法であり、各言語での実装方法が異なります。
ここでは、Python、TypeScript、Javaそれぞれのユニットテストの基本的な実装方法と、テストを書いた後の実行方法について詳しく説明します。
実行環境はmacOSです。

Python (pytest)

Pythonでは、pytestが非常に人気のあるユニットテストフレームワークです。
以下の手順で基本的なユニットテストを作成できます。

1.1 テストのセットアップ

まず、pytestをインストールします。ターミナルで以下のコマンドを実行します。

pip install pytest

1.2 テストコードの作成

簡単なテスト例を見てみましょう。以下のコードは数値の加算をテストするユニットテストです。
ファイル名はtest_で始まる、または_testで終わるものをテストファイルとして認識し、関数名がtest_で始まるものをテスト関数として検出します。
Python標準ライブラリのunittestとは異なり、クラス名の記載は不要です。

# calculator.py
def add(x, y):
    return x + y

# test_calculator.py
from calculator import add

def test_add():
    assert add(1, 1) == 2
    assert add(-1, 1) == 0
    assert add(-1, -1) == -2

1.3 テストの実行

全てのテストを実行するには、以下のコマンドを使用します。

pytest

特定のテストケースのみを実行する場合は、以下のコマンドを使用します。

pytest tests/test_calculator.py::test_add

テストが実行され、結果が表示されます。

1.4 アサーション

assertを使用して期待値と実際の値を比較し、条件が満たされない場合にテストを失敗させます。
以下に一般的なassertの使用例を示します。

# 等価性の確認
def test_equal():
    assert 1 + 1 == 2

# 不等価性の確認
def test_not_equal():
    assert 1 + 1 != 3

# 真偽の確認
def test_true():
    assert True

def test_false():
    assert not False

# リストの要素の確認
def test_in_list():
    assert 3 in [1, 2, 3]

# 例外の発生確認
def test_raises_exception():
    with pytest.raises(ZeroDivisionError):
        1 / 0

# 文字列の一致
def test_string():
    assert "hello" in "hello world"

1.5 フィクスチャ

テスト実行前後のセットアップを簡素化できます。
フィクスチャは関数として定義し、@pytest.fixture()デコレータを使用します。
scopeは実行の粒度を表します。

function: テストケース毎
class: テストクラス毎
module: テストファイル毎
session: pytestコマンド実行毎

yieldが実行対象を表し、yieldの後に前処理のメソッドを書くことで後処理に引数を渡すことができます。

import pytest

@pytest.fixture(scope="function")
def db_connection():
    # 前処理: データベース接続をセットアップ
    connection = setup_database_connection()
    yield connection
    # 後処理: テスト終了後のクリーンアップ
    connection.close()

TypeScript (Jest)

TypeScriptでは、Jestが広く使われているユニットテストフレームワークです。
以下の手順で基本的なユニットテストを作成できます。

2.1 テストのセットアップ

まず、Jestをインストールします。ターミナルで以下のコマンドを実行します。

npm install --save-dev jest @types/jest ts-jest

次に、Jestの設定ファイルを作成します。

npx ts-jest config:init

2.2 テストコードの作成

Jestがテスト対象として認識するファイルはデフォルト設定では以下の通りです。

  • ファイル名が以下のパターンのファイルをテストとして認識します。
    • .test.js /.test.ts
    • .spec.js /.spec.ts
    • testsディレクトリ内の全てのファイルをテストファイルとして認識します。
      以下は、簡単な加算機能をテストする例です。
// calculator.ts
export function add(x: number, y: number): number {
    return x + y;
}

// calculator.test.ts
import { add } from './calculator';

test('adds 1 + 1 to equal 2', () => {
    expect(add(1, 1)).toBe(2);
});

test('adds -1 + 1 to equal 0', () => {
    expect(add(-1, 1)).toBe(0);
});

2.3 テストの実行

テストを実行するには、以下のコマンドを使用します。

npx jest

テスト結果が表示され、全てのテストが通るか確認できます。

2.4 アサーション

Jestのexpect関数は、テストの期待値を設定するための関数で、さまざまなマッチャーを使って期待値を検証できます。
例えば、次のように使用します。

// 等価性の確認
expect(value).toBe(expectedValue);

// 不等価性の確認
expect(value).not.toBe(expectedValue);

// 含まれていることの確認
expect(array).toContain(item);

// 例外の発生確認
expect(() => {
    throw new Error('foo');
}).toThrow('foo');

2.5 フィクスチャ

JestではフィクスチャとしてbeforeEachやafterEachを使ってセットアップやクリーンアップを行います。
beforeEach: 各テストが実行される前に毎回実行される処理。
afterEach: 各テストが実行された後に毎回実行される処理。

Java (JUnit)

Javaでは、JUnitが広く使われているユニットテストフレームワークです。
以下の手順で基本的なユニットテストを作成できます。

3.1 テストのセットアップ

JUnitはMavenやGradleを使用してプロジェクトに追加します。Gradleの例を示します。

// build.gradle
dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter:5.8.2'
}

3.2 テストコードの作成

JUnit 5では、テストクラスは@Testアノテーションを持つメソッドを含む必要があります。以下に簡単な加算機能をテストする例を示します。

// Calculator.java
public class Calculator {
    public int add(int x, int y) {
        return x + y;
    }
}

// CalculatorTest.java
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;

class CalculatorTest {
    @Test
    void testAdd() {
        Calculator calculator = new Calculator();
        assertEquals(2, calculator.add(1, 1));
        assertEquals(0, calculator.add(-1, 1));
    }
}

3.3 テストの実行

JUnitはIDEのテスト機能やコマンドラインから実行できます。Gradleを使用している場合は、以下のコマンドでテストを実行します。

./gradlew test

3.4 アサーション

JUnitでは、assertEqualsやassertTrueなどのアサーションメソッドを使用して、期待される結果と実際の結果を比較します。

// 2つの値が等しいかどうかを確認
assertEquals(expected, actual);

// 2つの値が等しくないかどうかを確認
assertNotEquals(expected, actual);

//条件が真(true)であるかどうかを確認
assertTrue(condition);

//条件が偽(false)であるかどうかを確認
assertFalse(condition);

// オブジェクトがnullであるかどうかを確認
assertNull(obj);

// オブジェクトがnullでないことを確認
assertNotNull(obj);

// 2つの配列が等しいかどうか
assertArrayEquals(expected, actual);

3.5 フィクスチャ

JUnitでは、テストの前後でリソースのセットアップやクリーンアップを自動的に行うためのメソッドが用意されています。

@BeforeEach:各テストの実行前に実行されるメソッドに付与します。このメソッドで共通のテスト準備を行います。
@AfterEach:各テストの実行後に実行されるメソッドに付与します。リソースの解放や後処理を行います。
@BeforeAll:クラス全体のテストが始まる前に一度だけ実行されるメソッドに付与します。主に静的リソースの初期化に使います。
@AfterAll:クラス全体のテストが終わった後に一度だけ実行されるメソッドに付与します。主に静的リソースの解放に使います。

まとめ

この記事では、各言語でのユニットテストの基本的な実装方法と実行方法について詳細に説明しました。
皆さんの自動テストの第一歩をサポートするきっかけになれば幸いです。