はじめに
この実験では、Python の組み込み unittest モジュールの使い方を学びます。このモジュールは、テストを組織して実行するためのフレームワークを提供します。これは、コードが正しく動作することを保証するためのソフトウェア開発の重要な部分です。
また、基本的なテストケースを作成して実行し、予期されるエラーや例外をテストする方法も学びます。unittest モジュールは、テストスイートの作成、テストの実行、結果の検証のプロセスを簡素化します。この実験中に teststock.py ファイルが作成されます。
最初の単体テストの作成
Python の unittest モジュールは、テストを組織して実行するための構造化された方法を提供する強力なツールです。最初の単体テストを書き始める前に、いくつかの重要な概念を理解しましょう。テストフィクスチャは、setUp や tearDown のようなメソッドで、テストの前に環境を準備し、テスト後にクリーンアップするのに役立ちます。テストケースは個々のテスト単位で、テストスイートはテストケースの集合で、テストランナーはこれらのテストを実行し、結果を表示する役割を担います。
この最初のステップでは、stock.py ファイルにすでに定義されている Stock クラスの基本的なテストファイルを作成します。
- まず、
stock.pyファイルを開きましょう。これにより、テスト対象のStockクラスを理解するのに役立ちます。stock.pyのコードを見ることで、クラスの構造、持つ属性、提供するメソッドを確認できます。stock.pyファイルの内容を表示するには、ターミナルで以下のコマンドを実行します。
cat stock.py
- 次に、好みのテキストエディタを使用して、
teststock.pyという名前の新しいファイルを作成します。このファイルには、Stockクラスのテストケースが含まれます。teststock.pyファイルに記述する必要のあるコードは次のとおりです。
## teststock.py
import unittest
import stock
class TestStock(unittest.TestCase):
def test_create(self):
s = stock.Stock('GOOG', 100, 490.1)
self.assertEqual(s.name, 'GOOG')
self.assertEqual(s.shares, 100)
self.assertEqual(s.price, 490.1)
if __name__ == '__main__':
unittest.main()
このコードの主要なコンポーネントを分解してみましょう。
import unittest: この行は、Python でテストを書いて実行するために必要なツールとクラスを提供するunittestモジュールをインポートします。import stock: これは、Stockクラスを含むモジュールをインポートします。このインポートがなければ、テストコードでStockクラスにアクセスすることができません。class TestStock(unittest.TestCase):TestStockという名前の新しいクラスを作成し、unittest.TestCaseを継承します。これにより、TestStockクラスは複数のテストメソッドを含むことができるテストケースクラスになります。def test_create(self): これはテストメソッドです。unittestフレームワークでは、すべてのテストメソッドはtest_という接頭辞で始まる必要があります。このメソッドはStockクラスのインスタンスを作成し、assertEqualメソッドを使用してStockインスタンスの属性が期待される値と一致するかどうかを確認します。assertEqual: これはTestCaseクラスが提供するメソッドです。2 つの値が等しいかどうかをチェックします。等しくない場合、テストは失敗します。unittest.main(): このスクリプトが直接実行されると、unittest.main()はTestStockクラス内のすべてのテストメソッドを実行し、結果を表示します。
teststock.pyファイルにコードを書いたら、保存します。次に、ターミナルで以下のコマンドを実行してテストを実行します。
python3 teststock.py
次のような出力が表示されるはずです。
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
出力の単一のドット (.) は、1 つのテストが正常に通過したことを示します。テストが失敗した場合、ドットの代わりに F が表示され、テストで何が問題になったかに関する詳細情報も表示されます。この出力を使って、コードが期待どおりに動作しているか、修正が必要な問題があるかをすぐに確認できます。
テストケースの拡張
基本的なテストケースを作成したので、次はテストの範囲を拡大しましょう。さらに多くのテストを追加することで、Stock クラスの残りの機能を網羅することができます。これにより、クラスのすべての側面が期待どおりに動作することを保証できます。TestStock クラスを修正して、いくつかのメソッドとプロパティのテストを追加します。
teststock.pyファイルを開きます。TestStockクラスの中に、いくつかの新しいテストメソッドを追加します。これらのメソッドは、Stockクラスの異なる部分をテストします。追加する必要のあるコードは次のとおりです。
def test_create_keyword_args(self):
s = stock.Stock(name='GOOG', shares=100, price=490.1)
self.assertEqual(s.name, 'GOOG')
self.assertEqual(s.shares, 100)
self.assertEqual(s.price, 490.1)
def test_cost(self):
s = stock.Stock('GOOG', 100, 490.1)
self.assertEqual(s.cost, 49010.0)
def test_sell(self):
s = stock.Stock('GOOG', 100, 490.1)
s.sell(20)
self.assertEqual(s.shares, 80)
def test_from_row(self):
row = ['GOOG', '100', '490.1']
s = stock.Stock.from_row(row)
self.assertEqual(s.name, 'GOOG')
self.assertEqual(s.shares, 100)
self.assertEqual(s.price, 490.1)
def test_repr(self):
s = stock.Stock('GOOG', 100, 490.1)
self.assertEqual(repr(s), "Stock('GOOG', 100, 490.1)")
def test_eq(self):
s1 = stock.Stock('GOOG', 100, 490.1)
s2 = stock.Stock('GOOG', 100, 490.1)
self.assertEqual(s1, s2)
これらの各テストが何を行っているか、もう少し詳しく見てみましょう。
test_create_keyword_args: このテストは、キーワード引数を使用してStockオブジェクトを作成できるかどうかをチェックします。オブジェクトの属性が正しく設定されていることを検証します。test_cost: このテストは、Stockオブジェクトのcostプロパティが正しい値を返すかどうかをチェックします。この値は、株式数に価格を掛けたものとして計算されます。test_sell: このテストは、Stockオブジェクトのsell()メソッドが一部の株式を売却した後に株式数を正しく更新するかどうかをチェックします。test_from_row: このテストは、from_row()クラスメソッドがデータ行から新しいStockインスタンスを作成できるかどうかをチェックします。test_repr: このテストは、Stockオブジェクトの__repr__()メソッドが期待される文字列表現を返すかどうかをチェックします。test_eq: このテストは、__eq__()メソッドが 2 つのStockオブジェクトを正しく比較して等しいかどうかを判断できるかどうかをチェックします。
- これらのテストメソッドを追加したら、
teststock.pyファイルを保存します。次に、ターミナルで以下のコマンドを使用してテストを再度実行します。
python3 teststock.py
すべてのテストが合格した場合、次のような出力が表示されるはずです。
......
----------------------------------------------------------------------
Ran 7 tests in 0.001s
OK
出力の 7 つのドットは、それぞれのテストを表しています。各ドットは、テストが正常に通過したことを示します。したがって、7 つのドットが表示された場合は、7 つのテストすべてが合格したことを意味します。
例外のテスト
テストはソフトウェア開発の重要な部分であり、その重要な側面の 1 つは、コードがエラー状況を適切に処理できることを保証することです。Python では、unittest モジュールが特定の例外が期待通りに発生するかどうかをテストする便利な方法を提供しています。
teststock.pyファイルを開きます。例外をチェックするためのテストメソッドを追加します。これらのテストは、無効な入力に遭遇したときにコードが正しく動作することを確認するのに役立ちます。
def test_shares_type(self):
s = stock.Stock('GOOG', 100, 490.1)
with self.assertRaises(TypeError):
s.shares = '50'
def test_shares_value(self):
s = stock.Stock('GOOG', 100, 490.1)
with self.assertRaises(ValueError):
s.shares = -50
def test_price_type(self):
s = stock.Stock('GOOG', 100, 490.1)
with self.assertRaises(TypeError):
s.price = '490.1'
def test_price_value(self):
s = stock.Stock('GOOG', 100, 490.1)
with self.assertRaises(ValueError):
s.price = -490.1
def test_attribute_error(self):
s = stock.Stock('GOOG', 100, 490.1)
with self.assertRaises(AttributeError):
s.share = 100 ## 'share' is incorrect, should be 'shares'
これらの例外テストがどのように動作するかを理解しましょう。
with self.assertRaises(ExceptionType):文はコンテキストマネージャを作成します。このコンテキストマネージャは、withブロック内のコードが指定された例外を発生させるかどうかをチェックします。withブロック内で期待される例外が発生した場合、テストは合格します。これは、コードが無効な入力を正しく検出し、適切なエラーを発生させていることを意味します。- 例外が発生しないか、異なる例外が発生した場合、テストは失敗します。これは、コードが無効な入力を期待通りに処理していない可能性があることを示しています。
これらのテストは、以下のシナリオを検証するように設計されています。
shares属性を文字列に設定すると、sharesは数値である必要があるため、TypeErrorが発生するはずです。shares属性を負の数に設定すると、株式数は負にすることができないため、ValueErrorが発生するはずです。price属性を文字列に設定すると、priceは数値である必要があるため、TypeErrorが発生するはずです。price属性を負の数に設定すると、価格は負にすることができないため、ValueErrorが発生するはずです。- 存在しない属性
share(末尾の 's' が欠けている)を設定しようとすると、正しい属性名はsharesであるため、AttributeErrorが発生するはずです。
- これらのテストメソッドを追加したら、
teststock.pyファイルを保存します。次に、ターミナルで以下のコマンドを使用してすべてのテストを実行します。
python3 teststock.py
すべてが正しく動作している場合、12 のテストすべてが合格したことを示す出力が表示されるはずです。出力は次のようになります。
............
----------------------------------------------------------------------
Ran 12 tests in 0.002s
OK
12 個のドットは、これまでに書いたすべてのテストを表しています。前のステップで 7 つのテストがあり、ここで 5 つの新しいテストを追加しました。この出力は、コードが例外を期待通りに処理していることを示しており、十分にテストされたプログラムの良い兆しです。
選択したテストの実行とテスト自動検出の使用
Python の unittest モジュールは、コードを効果的にテストするための強力なツールです。特定のテストを実行したり、プロジェクト内のすべてのテストを自動的に検出して実行したりするいくつかの方法を提供します。これは、テスト中にコードの特定の部分に焦点を当てたり、プロジェクト全体のテストスイートをすばやく確認したりするのに非常に便利です。
特定のテストの実行
場合によっては、テストスイート全体ではなく、特定のテストメソッドやテストクラスのみを実行したいことがあります。これは、unittest モジュールでパターンオプションを使用することで実現できます。これにより、どのテストを実行するかをより細かく制御でき、コードの特定の部分をデバッグする際に便利です。
Stockオブジェクトの作成に関連するテストのみを実行するには:
python3 -m unittest teststock.TestStock.test_create
このコマンドでは、python3 -m unittest は Python に unittest モジュールを実行するよう指示します。teststock はテストファイルの名前、TestStock はテストクラスの名前、test_create は実行したい特定のテストメソッドです。このコマンドを実行することで、Stock オブジェクトの作成に関連するコードが期待通りに動作しているかをすばやく確認できます。
TestStockクラス内のすべてのテストを実行するには:
python3 -m unittest teststock.TestStock
ここでは、特定のテストメソッド名を省略しています。したがって、このコマンドは teststock ファイル内の TestStock クラス内のすべてのテストメソッドを実行します。これは、Stock オブジェクトのテストケースの全体的な機能を確認したい場合に便利です。
テスト自動検出の使用
unittest モジュールは、プロジェクト内のすべてのテストファイルを自動的に検出して実行することができます。これにより、特に多数のテストファイルがある大規模なプロジェクトで、実行する各テストファイルを手動で指定する手間を省くことができます。
- 現在のファイルをテスト自動検出の命名パターンに従うようにリネームする:
mv teststock.py test_stock.py
unittest のテスト自動検出メカニズムは、test_*.py という命名パターンに従うファイルを探します。ファイルを test_stock.py にリネームすることで、unittest モジュールがこのファイル内のテストを見つけて実行しやすくなります。
- テスト自動検出を実行する:
python3 -m unittest discover
このコマンドは、unittest モジュールに、現在のディレクトリ内で test_*.py パターンに一致するすべてのテストファイルを自動的に検出して実行するよう指示します。ディレクトリを検索し、一致するファイル内に見つかったすべてのテストケースを実行します。
- テストを検索するディレクトリを指定することもできます:
python3 -m unittest discover -s . -p "test_*.py"
ここで:
-s .は、検出を開始するディレクトリを指定します(この場合は現在のディレクトリ)。ドット (.) は現在のディレクトリを表します。別の場所でテストを検索したい場合は、これを別のディレクトリパスに変更することができます。-p "test_*.py"は、テストファイルを一致させるパターンです。これにより、test_で始まり、.py拡張子を持つファイルのみがテストファイルとして扱われます。
前と同様に、12 のテストすべてが実行され、合格するはずです。
- 実験との一貫性のために、ファイルを元の名前に戻す:
mv test_stock.py teststock.py
テスト自動検出を実行した後、実験環境を一貫させるためにファイルを元の名前に戻します。
テスト自動検出を使用することで、各テストファイルを個別に指定することなく、プロジェクト内のすべてのテストを簡単に実行できます。これにより、テストプロセスがより効率的になり、エラーが発生しにくくなります。
まとめ
この実験では、Python の unittest モジュールを使用して自動テストを作成し、実行する方法を学びました。unittest.TestCase クラスを拡張して基本的なテストケースを作成し、クラスのメソッドとプロパティの正常な機能を検証するテストを書き、エラー条件で適切な例外が発生することを確認するテストを作成しました。また、特定のテストを実行する方法とテスト自動検出の使用方法も学びました。
単体テスト (Unit testing) はソフトウェア開発における基本的なスキルであり、コードの信頼性と正確性を保証します。十分なテストを書くことで、バグを早期に発見することができ、コードの動作に自信を持つことができます。Python アプリケーションを開発する際には、テスト駆動開発 (Test-Driven Development, TDD) アプローチを採用し、機能を実装する前にテストを書くことで、より堅牢で保守可能なコードを作成することを検討してください。