cre8cre8
AskMe♥

Python3でunittestを導入してみる

Python3でテスト駆動(TDD)したいんだけど、ユニットテストってどうやって書くんだろう…
こんばんわ。みんなのアイドル(?)たくちゃんです。
というわけで、Python3でユニットテストするにはどうすればよいのかを導入から詳しく書いていきますね。
みんなテストコード書いてハッピーハッピーしようぜ!!

テスト対象のコード

先に実装を書いちゃうので、TDDのお作法から外れちゃうのですが、
ユニットテストの勉強ってことで勘弁してくだしぁ…(´;ω;`)ブワッ

みんな大好き!アニメのお気に入りを管理するプログラムを例にして、ユニットテストの使い方を覚えていきましょう!

anime.py
1class Anime:
2    """
3    お気に入りのアニメを登録する
4    """
5
6    def __init__(self):
7        self.favorite_animes = []
8
9    def favorite(self, anime):
10        """
11        アニメをお気に入りに登録する。
12        すでに登録済みのアニメだった場合は追加しない
13
14        :param str anime: お気に入りに登録するアニメのタイトル
15        """
16
17        if anime not in self.favorite_animes:
18            self.favorite_animes.append(anime)
19
20        return self
21
22    def least_favorite(self, anime):
23        """
24        アニメをお気に入りから削除する。
25        未登録のアニメだった場合は例外を送出して呼び出し元へ知らせる
26
27        :param str anime: お気に入りから削除するアニメのタイトル
28        """
29
30        if anime in self.favorite_animes:
31            self.favorite_animes.remove(anime)
32        else:
33            raise Exception('指定したアニメはお気に入りリストに存在しません')
34
35        return self

お気に入りのアニメを管理するプログラムをサンプルにして、各ユニットテストを書いていきましょう。
Animeクラスはインスタンスを生成されると、メンバ変数にお気に入りアニメを保存するリストを確保します。

favoriteメソッドにアニメのタイトルを入れるとお気に入りリストに追加され、
least_favoriteメソッドにアニメのタイトルを入れるとお気に入りリストから削除されます。

同じタイトルを多重登録されないように、favoriteメソッドでチェックしています。
また、存在しないタイトルをleast_favoriteメソッドで削除しようとすると例外を送出します。

メソッドチェーンで書けるように自身のインスタンスを返しています。

unittestを書いていく

unittestはPython3で標準用意されているので、インストールの必要はありません。
なんと便利な…私が初めてユニットテストを本格的に書いたのがPHPUnitだったのでこの便利さは超ありがたいです!
それでは、ガシガシとテストコードを書いてきましょう!
(最近のPHPUnitは導入が簡単になりましたが、当時はインストールするだけでも敷居が高かった記憶です...)

test_anime.py
1import unittest
2from anime import Anime
3
4class AnimeTest(unittest.TestCase):
5
6    " easy_to_use_anime_instanceで生成したときの初期お気に入りリストのデータ "
7    easy_to_use_anime_titles = ['成恵の世界', 'ぱにぽにだっしゅ!', 'らき☆すた']
8
9    def easy_to_use_anime_instance(self):
10        """
11        テストで使いやすいように初期データを登録したAnimeインスタンスを返す
12        :return Anime
13        """
14        anime = Anime()
15        for anime_title in self.easy_to_use_anime_titles:
16            anime.favorite(anime_title)
17        return anime
18
19    def test_single_call_favorite(self):
20        """
21        favoriteメソッドを1回だけコールして正常に登録できたかテストする
22        """
23        anime_title = '日常'
24        anime = Anime()
25        anime.favorite(anime_title)
26
27        self.assertEqual(anime_title, anime.favorite_animes[0])
28
29    def test_multiple_call_favorite(self):
30        """
31        favoriteメソッドを複数回コールして正常に登録できたかテストする
32        複数回コールは easy_to_use_anime_instance メソッドで実施している
33        """
34        anime = self.easy_to_use_anime_instance()
35        self.assertEqual(self.easy_to_use_anime_titles, anime.favorite_animes)
36
37
38    def test_favorite_send_duplication_anime(self):
39        """
40        お気に入りアニメのタイトルを重複して登録されないかテストする
41        """
42        anime_title = '日常'
43        anime = Anime()
44        anime.favorite(anime_title)
45        anime.favorite(anime_title)
46
47        self.assertEqual(len(anime.favorite_animes), 1)
48        self.assertEqual(anime_title, anime.favorite_animes[0])
49
50
51    def test_least_favorite(self):
52        """
53        お気に入りアニメリストから指定のアニメが正常に消えるかテスト
54        easy_to_use_anime_instance を使って初期データを用意している。
55        """
56        anime = self.easy_to_use_anime_instance()
57        anime.least_favorite('ぱにぽにだっしゅ!')
58
59        self.assertEqual(len(anime.favorite_animes), 2)
60        self.assertEqual(['成恵の世界', 'らき☆すた'], anime.favorite_animes)
61
62
63    def test_least_favorite_with_nodata(self):
64        """
65        お気に入りアニメリストに存在しないアニメを削除しようとしたときに
66        例外が送出されるかテスト
67        """
68        anime = Anime()
69        with self.assertRaises(Exception) as e:
70            anime.least_favorite('日常')
71
72        self.assertEqual('指定したアニメはお気に入りリストに存在しません', e.exception.args[0])
73

私の好きなアニメがバレちゃいましたね…そこ、時代が古いとか言わないの(´・ω・`)
テスト検証用のメソッドはたくさんあるのですが、とりあえず、
self.assertEqual と self.assertRaises を使いこなせるようになると
ユニットテストでほぼすべてのテストケースを記述できるようになります。
他のテストメソッドは便利にしてくれたり、見やすくしてくれるようにしてくれるものがほとんどなので。

それでは、テストを実行してみましょう。以下のコマンドでテストを実行できます。

shell
1python3 -m unittest test_anime.py

では各テストメソッドの解説をしますね。

self.assertEqual

このメソッドは引数に渡された2つの値が同一かどうかテストします。
今回のサンプルでは、アニメタイトルが正常に登録できているかどうか(str)と、
登録した結果リストの中身の個数が意図した個数になっているか(int)で使っていますね。
型が不一致のときはテストが失敗します。
試しに、下記のテストコードをtest_anime.pyに追加してテストを実行してみてください。

test_anime.py
1def test_type_mismatch(self):
2    self.assertEqual(1, '1')

self.assertRaises

このテストメソッドは例外のテストに使います。
self.assertEqualのように1行で記述することもできるのですが、with構文で書くほうが便利ですので、
with構文を使って書いています。

test_anime.py
1with self.assertRaises(Exception) as e:
2    anime.least_favorite('日常')
with構文の直下に書いているanime.least_favorite('日常')で意図した例外が発生するとこのテストはパスします。
また、例外のメッセージなどもテストしたい場合、 as e と記述すると 変数e に発生した例外をいれてくれます。
変数e に保存された例外の取り出し方はe.exceptionで取り出せます。
今回は
self.assertEqual('指定したアニメはお気に入りリストに存在しません', e.exception.args[0])として、
メッセージも意図したものが入っているかテストしています。

まとめ

今回はそのunittestの使い方をマスターしました。
それではおさらいです。unittestを使うためには

Python3には他のテストツールとして、noseやpytestなどありますが、
標準で用意されているunittestでもTDDを行うことができます。
unittest/nose/pytestの違いを理解して開発するのも楽しいと思いますので、
そのうちまとめようと思います。

≪ 前の記事
s3で月10円程度webサイトを公開する
次の記事 ≫
NginxとunicornでRails動かしてたらsockがNo such fileと言われたとき

いいねやコメントを送っていただけると中の人がしっぽ振って大喜びします♪

あなたへのおすすめの記事