Python単体テスト チュートリアルとベストプラクティス

By JoeVu, at: 2024年6月3日11:13

Estimated Reading Time: __READING_TIME__ minutes

Python Unittest Tutorial and Best Practices
Python Unittest Tutorial and Best Practices

はじめに

 

概要

 

At Glintecoでは、ユニットテスト/統合テスト/自動化テスト/ストレステストは、すべてのプロジェクトにおいて非常に重要です。私たちは「優れた開発者は、自身のコードに対するテストを書かなければならない」と信じています。

 

ユニットテストは、ソフトウェア開発における基本的な実践であり、ソフトウェアアプリケーションの個々のユニットまたはコンポーネントをテストして、期待通りに動作することを確認することを含みます。ユニットとは、ソフトウェアの最小限でテスト可能な部分であり、通常は関数またはメソッドです。これらのユニットを分離してテストすることにより、開発者は開発サイクルの早期にバグを特定して修正し、より堅牢で信頼性の高いソフトウェアを作成できます。

 

ユニットテストは重要です。なぜなら、以下のような利点があるからです。

 

  • コードの正確性を検証します。
     
  • 変更によって既存の機能が壊れないようにすることで、コードのリファクタリングを容易にします。
     
  • コードの動作方法を示すコードのドキュメントとして機能します。
     
  • 早期にバグを発見し、修正コストを削減します。
     
  • ソースコードの品質を向上させます。
     

多くの理由により、多くのシニア開発者がこれをうまく管理することはできませんが、プログラミングの専門知識を向上させるためには、ユニットテストを書くことが不可欠です。

 

目的

 

この記事では、Pythonのunittestフレームワークを使用してユニットテストを作成および実行するための完全なガイドを提供します(pytestについてはここでは説明しません。pytestとunittestの比較記事があります)。初心者でも経験豊富な開発者でも、このチュートリアルはunittestの基本を理解し、効果的なテストを作成するためのベストプラクティスを学ぶのに役立ちます。

 

この記事の最後には、以下ができるようになります。

 

  • unittestテストケースの基本構造を理解します。
     
  • テストの作成、実行、整理方法を学びます。
     
  • テストフィクスチャ、モッキング、テストスイートなどの高度な機能について探ります。
     
  • テストの品質と保守性を向上させるためのベストプラクティスを発見します。
     
  • 一般的な落とし穴とその回避方法を特定します。
     

unittestを使用したユニットテストは、コードの信頼性と保守性を確保するための強力な方法であり、より良い製品につながります。unittestの冒険を始めましょう。

 

Unittest入門

 

インストール

 

unittestはPythonの標準ライブラリに含まれているため、Pythonでのユニットテストを開始するために追加のパッケージをインストールする必要はありません。スクリプトにunittestをインポートするだけで、テストを作成する準備が整います。

 

基本構造

 

unittestフレームワークの中心はTestCaseクラスです。テストケースはunittest.TestCaseをサブクラス化して作成され、個々のテストメソッドはこのクラス内で定義されます。各テストメソッドはtestという単語で始まる必要があります。これにより、unittestテストランナーによって自動的に認識され、実行されます。これは少しPythonicではないように見えますね?

 

以下は、unittestテストケースの基本構造です。このファイルをtest.pyという名前で保存してください。

 

import unittest

def count_e_letters(name):
    count = 0
    for letter in name:
        if letter == 'e':
            count += 1
    return count

class TestCountELetters(unittest.TestCase):
    def test_count_e_letters(self):
        count = count_e_letters("Joe")
        self.assertEqual(1, count)

if __name__ == '__main__':
    unittest.main()

 

 

テストの実行

 

テストを実行するには、スクリプトを保存し、コマンドラインから実行します。

 

❯ python test.py
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK


テストが失敗すると、unittestは期待される結果と実際の結果を含む、失敗に関する詳細情報を提供し、問題の診断に役立ちます。

 

❯ python -m unittest tests/test_strings.py
F.
======================================================================
FAIL: test_count_e_letters_with_a_none_input (tests.test_strings.TestCountELetters.test_count_e_letters_with_a_none_input)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/joe/Documents/WORK/GLINTECO/PROJECTS/INTERNAL/samples/unittest_tutorials/tests/test_strings.py", line 13, in test_count_e_letters_with_a_none_input
    self.assertEqual(1, count)
AssertionError: 1 != 0


これらの基本事項を理解すれば、unittestを使用して単純なテストを作成し始めることができます。次のセクションでは、セットアップとティアダウンメソッドを使用してテスト環境を効果的に管理するテストケースの作成について詳しく説明します。

 

テストケースの作成

 

セットアップティアダウン

 

ユニットテストでは、テストの実行前に特定の環境を準備し、後処理を行うことが一般的です。unittestは、これらを処理するためにsetUpメソッドとtearDownメソッドを提供します。setUpメソッドは、各テストメソッドの前に呼び出され、テスト間で共有される状態を設定し、tearDownメソッドは各テストメソッドの後に呼び出され、後処理を行います。これはRubyに似ています

 

例を以下に示します。

 

import unittest

class TestExample(unittest.TestCase):
    def setUp(self):
        self.user = User()

    def tearDown(self):
        self.user = None

    def test_name(self):
        self.assertEqual("Joe", self.user.name)

    def test_action(self):
        self.assertEqual("Playing Game", self.user.action())


上記からわかるように:

 

  • setUpメソッドは、各テストの前にself.userを新しいユーザーとして初期化します。
     
  • tearDownメソッドは、各テストの後にself.userNoneに設定することで後処理を行います。
     
  • 2つのテストメソッド(test_nametest_action)は、self.userを使用してアサーションを実行します。

 

setUp/tearDownsetUpClass/tearDownClassの違いは何ですか?

 

setUptearDown

 

  • 目的setUptearDownメソッドは、各テストメソッドに必要なリソースのセットアップとクリーンアップに使用されます。
     
  • 実行setUpは各テストメソッドの実行前に呼び出され、tearDownは各テストメソッドの終了後に呼び出されます。
     
  • スコープ:これらのメソッドのセットアップとティアダウンのアクションは、TestCaseクラス内の各テストメソッドに適用されます。つまり、複数のテストメソッドがある場合、setUptearDownは複数回(各テストメソッドごとに1回)実行されます。

 
import unittest

class TestExample(unittest.TestCase):
    def setUp(self):
        self.number = 1
        print("Setting up before a test method")

    def tearDown(self):
        self.number = None
        print("Tearing down after a test method")

    def test_addition(self):
        self.assertEqual(self.number + 1, 2)

    def test_subtraction(self):
        self.assertEqual(self.number - 1, 0)

 

出力

 

Setting up before a test method
Tearing down after a test method
Setting up before a test method
Tearing down after a test method

 

setUpClasstearDownClass

 

  • 目的setUpClasstearDownClassは、個々のテストメソッドだけでなく、テストケースクラス全体に必要なリソースのセットアップとクリーンアップに使用されます。
     
  • 実行setUpClassはテストメソッドが実行される前に1回呼び出され、tearDownClassはすべてのテストメソッドの実行が終了した後に1回呼び出されます。
     
  • スコープ:これらのメソッドのセットアップとティアダウンのアクションは、テストケースクラス全体に適用されます。つまり、複数のテストメソッドがある場合、setUpClasstearDownClassはクラス全体に対して1回だけ実行されます。
     

import unittest

class TestExample(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.shared_resource = "Shared Resource"
        print("Setting up class resources")

    @classmethod
    def tearDownClass(cls):
        cls.shared_resource = None
        print("Tearing down class resources")

    def test_use_shared_resource(self):
        self.assertEqual(self.shared_resource, "Shared Resource")

    def test_another_use_of_shared_resource(self):
        self.assertEqual(self.shared_resource, "Shared Resource")


出力:

 

Setting up class resources
Tearing down class resources

 

アサーション

 

アサーションは、テストの主要な構成要素です。条件が真かどうかをチェックし、そうでない場合は、テストが失敗したことを示すエラーを発生させます。unittestは、さまざまな条件をチェックするためのさまざまなアサーションメソッドを提供します。一般的に使用されるアサーションをいくつか示します。

 

  • assertEqual(a, b)abと等しいかどうかをチェックします。
     
  • assertNotEqual(a, b)abと等しくないかどうかをチェックします。
     
  • assertTrue(x)xTrueかどうかをチェックします。
     
  • assertFalse(x)xFalseかどうかをチェックします。
     
  • assertIs(a, b)abかどうかをチェックします。
     
  • assertIsNot(a, b)abではないかどうかをチェックします。
     
  • assertIsNone(x)xNoneかどうかをチェックします。
     
  • assertIsNotNone(x)xNoneではないかどうかをチェックします。
     
  • assertIn(a, b)ab内にあるかどうかをチェックします。
     
  • assertNotIn(a, b)ab内になくかどうかをチェックします。
     
  • assertIsInstance(a, b)abのインスタンスかどうかをチェックします。
     
  • assertNotIsInstance(a, b)abのインスタンスではないかどうかをチェックします。
     

これらのアサーションステートメントを見てください。camelCaseの関数を含んでいるため、これもPythonicではないように見えます。

 

さまざまなアサーションを使用した例を以下に示します。

 

import unittest

class TestAssertions(unittest.TestCase):
    def test_assertions(self):
        self.assertEqual(1 + 1, 2)
        self.assertNotEqual(2 + 2, 5)
        self.assertTrue(3 < 5)
        self.assertFalse(5 < 3)
        self.assertIs(None, None)
        self.assertIsNot(1, None)
        self.assertIsNone(None)
        self.assertIsNotNone(1)
        self.assertIn(3, [1, 2, 3])
        self.assertNotIn(4, [1, 2, 3])
        self.assertIsInstance(3, int)
        self.assertNotIsInstance(3, str)

 

テストの実行

 

テストはいくつかの方法で実行できます。

 

  1. コマンドライン:コマンドラインからテストスクリプトを直接実行します。python test_script.py
     

  2. テストの検出unittestの組み込みテスト検出メカニズムを使用して、テストを自動的に見つけて実行します。これは、多くのテストファイルを持つ大規模なプロジェクトに役立ちます。

    python -m unittest discover

    このコマンドは、現在のディレクトリとそのサブディレクトリでテストモジュールを検索します。
     

  3. IDEからの実行:PyCharm、VS Code、Eclipseなどのほとんどの統合開発環境(IDE)は、unittestテストの実行をサポートしており、テストの実行とデバッグのための便利なグラフィカルインターフェースを提供します。

 

project/
    ├── src/
    │   └── example.py
    └── tests/
        ├── __init__.py
        └── test_example.py


tests/test_example.py内:

 

import unittest
from src.example import add

class TestMathFunctions(unittest.TestCase):
    def test_add(self):
        self.assertEqual(add(1, 2), 3)


次を使用してテストを実行します。

 

python -m unittest discover -s tests


このコマンドは、unittesttestsディレクトリ内のすべてのテストを検出して実行するように指示します。

writing unit tests in python

 

高度な機能

 

モッキング

 

モッキングは、ユニットテストで使用されるテクニックであり、実際のオブジェクトを、実際のオブジェクトの動作をシミュレートするモックオブジェクトに置き換えます。これは、テスト対象のコードをその依存関係から分離する場合に特に役立ちます。

 

サンプルユースケース:関数AとBの2つの関数があります。関数Aのテストをすでに書いており、関数Bは関数Aを呼び出します。関数Bのユニットテストを書く必要があります。モックを使用する必要があります。

 

Pythonのunittest.mockモジュールは、オブジェクトをモックするための強力なフレームワークを提供します。unittest.mockの使用方法の例を以下に示します。

 

from unittest import TestCase
from unittest.mock import MagicMock, patch

 

class TestMocking(TestCase):
    @patch('path.to.module.ClassName')
    def test_mocking(self, mock_class):
        instance = mock_class.return_value
        instance.method.return_value = 'mocked!'

        result = instance.method()
        self.assertEqual(result, 'mocked!')
        mock_class.assert_called_once()
        instance.method.assert_called_once()


この例では:

 

  • @patch('path.to.module.ClassName')は、path.to.moduleClassNameをモックオブジェクトに置き換えます。
     
  • mock_class.return_valueは、クラスのモックインスタンスです。
     
  • instance.method.return_value = 'mocked!'は、メソッドの戻り値を'mocked!'に設定します。

 

テストスイート

 

テストスイートを使用すると、複数のテストケースとテストメソッドを単一のスイートにグループ化し、まとめて実行できます。これは、テストの整理や特定のテストのサブセットの実行に役立ちます。

 

テストスイートの作成と実行方法の例を以下に示します。

 

import unittest


class TestMath(unittest.TestCase):
    def test_add(self):
        self.assertEqual(1 + 1, 2)

    def test_subtract(self):
        self.assertEqual(2 - 1, 1)


class TestStringMethods(unittest.TestCase):
    def test_upper(self):
        self.assertEqual('foo'.upper(), 'FOO')

    def test_isupper(self):
        self.assertTrue('FOO'.isupper())
        self.assertFalse('Foo'.isupper())


if __name__ == '__main__':
    suite = unittest.TestSuite()
    suite.addTest(TestMath('test_add'))
    suite.addTest(TestMath('test_subtract'))
    suite.addTest(TestStringMethods('test_upper'))
    suite.addTest(TestStringMethods('test_isupper'))

    runner = unittest.TextTestRunner()
    runner.run(suite)


この例では:

 

  • unittest.TestSuite()はテストスイートを作成します。
     
  • suite.addTest(TestMath('test_add'))は、個々のテストメソッドをスイートに追加します。
     
  • unittest.TextTestRunner()はテストスイートを実行します。

 

テストのスキップ

 

特定のテストをスキップしたい場合があります。unittestは、この目的のためにデコレーターを提供します。

 

  • @unittest.skip(reason):テストを無条件にスキップします。
     
  • @unittest.skipIf(condition, reason):条件が真の場合、テストをスキップします。
     
  • @unittest.skipUnless(condition, reason):条件が偽の場合、テストをスキップします。
     

例を以下に示します。

 

import unittest

class TestExample(unittest.TestCase):
    @unittest.skip("demonstrating skipping")
    def test_skip(self):
        self.fail("shouldn't happen")

    @unittest.skipIf(1 == 1, "not testing this right now")
    def test_skip_if(self):
        self.fail("shouldn't happen")

    @unittest.skipUnless(1 == 0, "not testing this right now")
    def test_skip_unless(self):
        self.fail("shouldn't happen")


この例では、3つのテストすべてがさまざまな理由でスキップされます。

 

ベストプラクティス

 

ユニットテストでベストプラクティスを採用すると、テストの効果、保守性、信頼性を確保するのに役立ちます。unittestフレームワークを使用するための重要なベストプラクティスをいくつか紹介します。

 

テストカバレッジ

 

高いカバレッジを目指しましょう:大部分のコードがテストされるように、高いテストカバレッジを目指しましょう。coverage.pyなどのツールを使用して、コードのどの程度がテストによってカバーされているかを測定します。

 

pip install coverage
coverage run -m unittest discover
coverage report -m
Name                    Stmts   Miss  Cover   Missing
-----------------------------------------------------
libs/__init__.py            0      0   100%
libs/strings.py            19      8    58%   15-24
tests/test_strings.py      19      0
Tag list:
- unittest
- Mock
- pytest
- test suite
- python
- common mistakes
- best practices
- python common mistakes
- function tests
- class tests
- writing test
- tdd
- hdd
- stub

Related

Django Python

Read more
Python Unit Test

Read more
Outsourcing Experience

Read more

Subscribe

Subscribe to our newsletter and never miss out lastest news.