コードのテスト

コードをテストすることは非常に重要です。

テストコードを書くことに慣れ、このコードを並行して実行することは、現在、良い習慣と考えられています。この方法は賢明に使用され、コードの意図をより正確に定義し、より分離されたアーキテクチャを持つのに役立ちます。

いくつかの一般的なテストのルール:

  • テストユニットは1つの機能のほんの少しに集中し、それが正しいことを証明する必要があります。
  • 各試験ユニットは完全に独立していなければなりません。各テストは、呼び出される順番に関係なく、単独で実行できなければならず、テストスイート内でも実行できなければなりません。このルールの意味は、各テストに新しいデータセットがロードされていなければならず、後で何らかのクリーンアップを行わなければならない可能性があるということです。これは通常 setUp()tearDown() メソッドで処理されます。
  • テストを高速化するのは難しいです。 1回のテストに数ミリ秒以上の時間がかかる場合は、開発が遅くなるか、またはテストが望ましいほど頻繁に実行されません。場合によっては、複雑なデータ構造が必要であり、テストが実行されるたびにこのデータ構造をロードする必要があるため、テストを高速化することはできません。これらのより重いテストは、スケジュールされたタスクによって実行される別のテストスイートに保管し、必要に応じて他のすべてのテストを頻繁に実行します。
  • あなたのツールを学び、単一のテストまたはテストケースを実行する方法を学びます。 次に、モジュール内の関数を開発するときは、この関数のテストを頻繁に、理想的にはコードを保存すると自動的に実行します。
  • コーディングセッションの前に常に完全なテストスイートを実行してから、それをやり直してください。 これにより、残りのコードで何も壊れていないという確信が得られます。
  • コードを共有リポジトリにプッシュする前に、すべてのテストを実行するフックを実装することをお勧めします。
  • 開発セッションの途中で作業を中断しなければならない場合は、次に開発したいものについて壊れた単体テストを書くことをお勧めします。開発作業に戻ったときに、中断した続きへポインタを持っていくことができ、すぐに軌道に乗れるでしょう。
  • コードをデバッグする最初のステップは、バグを特定する新しいテストを書くことです。必ずしもそうであるとは限りませんが、これらのバグを捕まえるテストは、プロジェクトの中で最も価値のあるコードです。
  • テスト関数には長い記述的な名前を使用します。ここのスタイルガイドは、短い名前がしばしば好まれる実行コードとは少し異なります。なぜなら、テスト関数は決して明示的に呼び出されないからです。実行中のコードで square()sqr() でもOKですが、テストコードでは test_square_of_number_2(), test_square_negative_number() などの名前を使用します。これらの関数名は、テストが失敗したときに表示され、可能な限り説明的でなければなりません。
  • 何かがうまくいかなかったり、変更が必要な場合、コードに適切なテストセットがある場合、あなたや他のメンテナはテストスイートに大きく依存して、問題を修正したり、特定の動作を修正します。 したがって、テストコードは、実行中のコードと同じくらい、あるいはそれ以上に読み込まれます。 その目的がはっきりしない単体テストは、この場合はあまり役に立ちません。
  • テストコードのもう1つの使用方法は、新しい開発者を紹介することです。誰かがコードベースで作業しなければならない場合、関連するテストコードを実行して読むことが、しばしば彼らが始めるためにできる最善のことです。彼らは、最も困難が発生するホットスポット、およびコーナーケースを発見するでしょう。いくつかの機能を追加する必要がある場合は、最初にテストを追加して、新しい機能がインターフェイスにプラグインされていない現用パスでないことを確認します。

基本

ユニットテスト

unittest は、Pythonの標準ライブラリに含まれるバッテリー同梱のテストモジュールです。 そのAPIは、JUnit/nUnit/CppUnitシリーズのいずれかを使用している人にとっては馴染み深いものです。

テストケースを作成するには、unittest.TestCase をサブクラス化します。

import unittest

def fun(x):
    return x + 1

class MyTest(unittest.TestCase):
    def test(self):
        self.assertEqual(fun(3), 4)

Python 2.7の時点で、unittestには独自のテスト検出メカニズムも含まれています。

Doctest

doctest モジュールは、ドキュメンテーション文字列中のインタラクティブなPythonセッションのように見えるテキストを検索し、それらのセッションを実行して、表示されているとおりに動作することを確認します。

Doctestは、適切な単体テストとは異なるユースケースを持っています。通常、それは詳細は少なく、特殊なケースやあいまいな回帰バグは検出されません。 これらは、モジュールとそのコンポーネントの主な使用例を表現した文書として役立ちます。 ただし、doctestは、完全なテストスイートが実行されるたびに自動的に実行されます。

関数内の簡単なdoctest:

def square(x):
    """Return the square of x.

    >>> square(2)
    4
    >>> square(-2)
    4
    """

    return x * x

if __name__ == '__main__':
    import doctest
    doctest.testmod()

python module.py のようにコマンドラインからこのモジュールを実行すると、doctestは実行され、docstringに記述されているように動作していないものがあれば文句を言います。

ツール

py.test

py.testは、Pythonの標準的なunittestモジュールに代わるものではありません。

$ pip install pytest

完全に機能し、拡張可能なテストツールであるにもかかわらず、簡単な構文です。 テストスイートを作成するのは、以下の2つの機能を持つモジュールを作成するのと同じくらい簡単です。

# content of test_sample.py
def func(x):
    return x + 1

def test_answer():
    assert func(3) == 5

py.test コマンドを実行します

$ py.test
=========================== test session starts ============================
platform darwin -- Python 2.7.1 -- pytest-2.2.1
collecting ... collected 1 items

test_sample.py F

================================= FAILURES =================================
_______________________________ test_answer ________________________________

    def test_answer():
>       assert func(3) == 5
E       assert 4 == 5
E        +  where 4 = func(3)

test_sample.py:5: AssertionError
========================= 1 failed in 0.02 seconds =========================

unittestモジュールと同等の機能に必要な作業よりはるかに少ない作業です!

Nose

noseはunittestを拡張してテストを容易にします。

$ pip install nose

noseは、テストスイートを手動で作成する手間を省くための自動テスト検出機能を提供します。 また、xUnitと互換性のあるテスト出力、カバレッジレポート、テストの選択など、多数のプラグインを提供します。

tox

toxは、テスト環境の管理を自動化し、複数のインタープリタ構成に対してテストするためのツールです

$ pip install tox

toxを使用すると、単純なini形式の構成ファイルを使用して、複雑な複数パラメータのテスト行列を構成できます。

Unittest2

unittest2は、Python 2.7のunittestモジュールのバックポートであり、改良されたAPIと以前のバージョンのPythonよりも優れたアサーションを備えています。

Python 2.6以降を使用している場合は、pipでインストールできます

$ pip install unittest2

将来的にモジュールの新しいバージョンへの移植コードをより簡単にするために、unittestという名前でモジュールをインポートすることができます

import unittest2 as unittest

class MyTest(unittest.TestCase):
    ...

こうすることで、新しいPythonバージョンに切り替えてunittest2モジュールが不要になった場合でも、他のコードを変更することなくテストモジュールのインポートを変更することができます。

mock

unittest.mock はPythonでテストするためのライブラリです。 Python 3.3以降、これは 標準ライブラリ で利用可能です。

古いバージョンのPythonの場合:

$ pip install mock

テスト中のシステムの一部をモックオブジェクトに置き換え、それらがどのように使用されたかをアサーションすることができます。

For example, you can monkey-patch a method:

from mock import MagicMock
thing = ProductionClass()
thing.method = MagicMock(return_value=3)
thing.method(3, 4, 5, key='value')

thing.method.assert_called_with(3, 4, 5, key='value')

テスト中のモジュールのクラスやオブジェクトをモックするには、 patch デコレータを使います。 以下の例では、外部検索システムが、常に同じ結果を返すモック(ただし、テストの期間のみ)に置き換えられています。

def mock_search(self):
    class MockSearchQuerySet(SearchQuerySet):
        def __iter__(self):
            return iter(["foo", "bar", "baz"])
    return MockSearchQuerySet()

# SearchForm here refers to the imported class reference in myapp,
# not where the SearchForm class itself is imported from
@mock.patch('myapp.SearchForm.search', mock_search)
def test_new_watchlist_activities(self):
    # get_search_results runs a search and iterates over the result
    self.assertEqual(len(myapp.get_search_results(q="fish")), 3)

Mockには他の多くの方法があります。