今回は、テスト駆動開発の足掛けとして、pytestの使い方を紹介します。
テスト駆動開発とは、テストのことを第一に考えて実装する開発法のことです。
アジャイル開発のように、設計・開発・テストを短く繰り返す場合に、有効な開発手法とされています。
pytestは、Pythonをテスト実行するためのライブラリです。
入力値と期待する実行結果を記述し、正しく実行完了できるかどうかを検証することができます。
そのため、pytestを先に定義することで、pytestが正しく完了できるような実装をすることで、テスト駆動開発が成立します。
もちろん、開発が終わった後の、単体テスト・結合テストとしてpytestを作成することもできます。
前置きが長くなってしまいましたが、
そんなわけでpytestの使い方を学んで、手戻りのないオンスケの開発をしましょう!
ちなみに、Pythonでテスト駆動開発をする場合は、pytest以外にもunittestライブラリを使用した方法があります。
インストール
pytestのインストール方法です。
PyPlにあります通り、pipコマンドでインストールが可能です。
$ pip install pytest
anacondaをお使いの方は、anacondaでもパッケージが配布されています。
condaコマンドでインストール可能です。
$ conda install -c anaconda pytest
pytestの定義
pytestの書き方を紹介します。
その前に、今回は以下のPythonファイルに記述された関数をテストすることにします。
class Human:
def __init__(self, name):
self.name = name
def call(self):
str = 'My name is ' + self.name
return str
Humanクラスに、コンストラクタと3つのメソッドがあります。
これら全てのメソッドを定義するpytestを作成したいと思います。
結論、以下のようになります。
from main import Human
def test_init():
human = Human('Tanaka')
assert human.name == 'Tanaka'
def test_type_call():
human = Human('Yamada')
assert type(human.call()) == str
def test_var_call():
human = Human('Sato')
assert human.call() == 'My name is Sato'
では、こちらのpytestのコードを解説していきます。
pytestでは、関数名の先頭に”test_”と書かれたもののみが実行される関数となります。
”test_”をつけ忘れると、pytestを実行しても、実行の対象とならないため、注意してください。
この"test_"と名づけれた関数を1つのテストとみなします。
(なので、今回の場合は3つテストがあります。)
では、次に関数の中に注目したいと思います。
関数の中は、通常のPythonのコードと同様に、どのような処理を行うか記述します。
test_init関数の例だと、Humanクラスをインスタンス化しています。
関数の最後に、assert句があります。
assert句は、if文やtry文と同様な記法で記述します。
assert句には期待する結果を記載します。
assert句の結果=テストの結果という認識でOKです。
test_init関数の場合は、Humanクラスのnameに'Tanaka'が設定されているか、
test_type_call関数の場合は、call関数の返り値の型がStringか、
test_var_call関数の場合は、call関数の返り値が、'My name is Sato'か、
これらがテストの対象となります。
pytestの実行
では、pytestを実行してみましょう。
pytestの実行は、通常のPythonの実行とは異なり、pytestコマンドでPythonファイルを実行します。
$ pytest unit_test.py
実行結果は、以下になります。
$ pytest unit_test.py
================================= test session starts ==================================
platform darwin -- Python 3.8.8, pytest-6.2.3, py-1.10.0, pluggy-0.13.1
rootdir: /Users/ユーザー名/test/pytest
collected 3 items
unit_test.py ... [100%]
================================== 3 passed in 0.01s ===================================
このようになりました。
collected 3 item と記載されている通り、3つのtest関数の実行結果は、collectということになります。
最後の行にも、3 passed in 0.01s と表示されています。
関数の実行が0.01秒だったこともわかります。
では、次にテストに失敗してみましょう。
test_type_call関数のassert句を、以下のように変更してみました。
assert type(human.call()) == int
human.call()の返り値はString型なので、このテストは失敗になるはずですよね。
では、先程と同じpytestコマンドで実行してみます。
$ pytest unit_test.py
================================= test session starts ==================================
platform darwin -- Python 3.8.8, pytest-6.2.3, py-1.10.0, pluggy-0.13.1
rootdir: /Users/ユーザー名/test/pytest
collected 3 items
unit_test.py .F. [100%]
======================================= FAILURES =======================================
____________________________________ test_type_call ____________________________________
def test_type_call():
human = Human('Yamada')
> assert type(human.call()) == int
E AssertionError: assert <class 'str'> == int
E + where <class 'str'> = type('My name is Yamada')
E + where 'My name is Yamada' = <bound method Human.call of <main.Human object at 0x7fd88d3e7df0>>()
E + where <bound method Human.call of <main.Human object at 0x7fd88d3e7df0>> = <main.Human object at 0x7fd88d3e7df0>.call
unit_test.py:9: AssertionError
=============================== short test summary info ================================
FAILED unit_test.py::test_type_call - AssertionError: assert <class 'str'> == int
============================= 1 failed, 2 passed in 0.07s ==============================
予想通り、Failureとなりました。
.F. と記載されている通り、上から2番目のtest関数の実行が失敗しました。
FAILURESの中にも、どこでエラーが出たのか、何がだめだったのかが表示されていますね。
このようにして、pytestを実行します。
補足
ここまでは基本的な使い方でしたが、ここからはより実践的な使い方を紹介したいと思います。
ディレクトリを分ける
上述の紹介では、main.pyとunit_test.pyが同じ階層にありました。
ですが、同階層にファイルをおいていると、見栄えが良くないですし管理もしにくいです。
パッケージ化するならなおのこと、pytestのファイルは切り分けておいた方が良いです。
というわけで、ディレクトリの分け方と、その際のもう一つの実行方法を紹介します。
ディレクトリの分け方は、以下の通りです。
|- src
| |- __init__.py
| |- main.py
|
|- unit_test
| |- __init__.py
| |- unit_test.py
Pythonファイル自体は、先ほどと同じmain.pyとunit_test.pyですが、それをディレクトリで分けました。
そして、__init__.pyを作成しました。
__init__.pyを作らないとimportエラーが出るので、作るようにしてください。
__init__.pyの中身は、いずれも空でOKです。
unit_test.pyが参照するmain.pyの場所が変わったので、最初のimport文を以下のように変更します。
from src.main import Human
このimport文ですが、srcとunit_testの親ディレクトリで実行することを想定したものになっています。
変更がある場合は、適宜書き換えてください。
では、実行してみましょう。
$ ls
src unit_test
$ pytest unit_test/unit_test.py
================================= test session starts ==================================
platform darwin -- Python 3.8.8, pytest-6.2.3, py-1.10.0, pluggy-0.13.1
rootdir: /Users/ユーザー名/test/pytest
collected 3 items
unit_test/unit_test.py ... [100%]
================================== 3 passed in 0.02s ===================================
このように、先ほどと同様にテストが実行されました。
そのほか、
テスト時間を計測したり、処理を分けたい場合は、こちらを、
CircleCIと連携したい場合は、こちらを見てみてください。
とても参考になります。
Python学習におすすめの教材
Pythonについて、しっかりと基礎から学びたい、実践的なスキルを身につけたいと考えている方向けに、おすすめの学習教材を紹介します。
これらの教材は、実際に僕がPythonの学習に使った教材になっています。
現役シリコンバレーエンジニアが教えるPython 3 入門 + 応用 +アメリカのシリコンバレー流コードスタイル
現役シリコンバレーエンジニアが教えるPython入門!応用では、データ解析、データーベース、ネットワーク、暗号化、並列化、テスト、インフラ自動化、キューイングシステム、非同期処理など盛り沢山の内容です!
ベネッセが運営する動画学習サイトUdemyで視聴することができる動画教材です。
(セールの時には¥1,500程度で買えます!)
Pythonは自由度が高くて雑に書いても動きますが、その分コードスタイルがおろそかになりがちです。
正しいコードスタイルを身に着けておかなければ、Pythonプログラマとして活躍することはあり得ません。
この教材は、グローバルスタンダードなコードスタイルを正しく学びながら、アルゴリズムやサーバー構築など幅広くPythonの活用方法を学習することができます。
Pythonの学習で間違えたくない方や、趣味レベルのスキルから一歩抜け出したい方におすすめです。
エキスパートPythonプログラミング改訂2版 Pythonプログラミングのベストプラクティスを伝授
本書は、Pythonを使って仕事をしている開発者が普段どのようなツールやテクニックを用いて仕事をしているのか、また開発者が実際に現場で用いているベストプラクティスについて解説した書籍です。本書を読むことで、先進的なPythonプログラマが日常的に使用している開発ノウハウを学ぶことができます。
こちらの書籍は、様々なユースケースに対するベストプラクティスを学習することができる書籍です。
Python2系と3系の違いについてもよく取り上げられますので、実質どちらのバージョンにも対応しているといえます。
Pythonをうまく使いこなせていない気がする方、初心者の殻を破りたい方におすすめです。