- 環境
- 知見 (1) ランダムな情報を返す関数をテストする
— 関数
— テスト - 知見 (2) とある日付の前月を返す関数をテストする
— 関数
— テスト - 知見 (3) テスト結果を XML 形式で出力する
— メモ - 以上
環境
以下のような環境でやってます.
$ sw_vers ProductName: Mac OS X ProductVersion: 10.11.6 BuildVersion: 15G19009 $ python --version Python 3.6.4
知見 (1) ランダムな情報を返す関数をテストする
関数
以下のような関数を sample_1.py というファイルに作成しました.
import random def select_random_key(n): k = random.choice(range(n)) return "{0:02d}".format(k)
この関数は, 引数 n を渡すと, 0 から n – 1 までの数値を選んで, 0 パディングして返すだけの関数で, 実際に試してみると以下のような感じになります.
>>> def select_random_key(n): ... k = random.choice(range(n)) ... return "{0:02d}".format(k) ... >>> select_random_key(3) '01' >>> select_random_key(3) '02' >>> select_random_key(3) '00'
さて, これをテストする場合, mock を使って, 以下のようにテストを書きました.
テスト
以下のようなテストを tests/test_sample_1.py というファイルに作成しました.
import unittest import mock import sample_1 class SampleTest(unittest.TestCase): @mock.patch('random.choice') def test_select_random_key(self, random_call): random_call.return_value = 1 self.assertEqual(sample_1.select_random_key(3), '01')
mock.patch を利用して, random.choice()
関数の戻り値を指定した値 (1) で固定しました(random_call.return_value = 1
).
テストを実行すると, 以下のように意図した結果を返すことを確認しました.
$ python -m unittest tests.test_sample_1 -v test_select_random_key (tests.test_sample_1.SampleTest) ... ok ---------------------------------------------------------------------- Ran 1 test in 0.001s OK
知見 (2) とある日付の前月を返す関数をテストする
関数
以下のような関数を sample_2.py というファイルに作成しました.
from datetime import datetime, timedelta def get_last_month(): # 本日を取得 today = datetime.now() # 当月の初日 (1 日) を取得 this_month_first_day = datetime(today.year, today.month, 1) # 当月初日の 1 日前の日付 (前月) を取得 last_month_last_day = this_month_first_day + timedelta(days=-1) # 前月の情報から %Y (年) と %m (月) を取得 return last_month_last_day.strftime('%Y%m')
datetime 関数だけで前月を取得するというのが意外に面倒くさいということで, Pythonで先月を取得する – Qiita を参考にさせて頂きました. 前日なら datetime.timedelta() で取得出来るんですがね…
この関数を実行すると, 以下のように前月を YYYYMM
という文字列で返します.
>>> from datetime import datetime, timedelta >>> def get_last_month(): ... today = datetime.now() ... this_month_first_day = datetime(today.year, today.month, 1) ... last_month_last_day = this_month_first_day + timedelta(days=-1) ... return last_month_last_day.strftime('%Y%m') ... >>> get_last_month() '201802'
さて, これをどのようにテストするのか. 以下のように書けば良さそう.
mport unittest import sample_2 class SampleTest(unittest.TestCase): def test_get_last_month(self): self.assertEqual(sample_2.get_last_month(), '201802')
そう, 今月一杯 (2018 年 03 月) ならね… このテストを 4 月に実行すると 201803
が返ってきてしまうのでテストはコケてしとらす.
テスト
では, どうすれば良いのか. 先程と同様に mock を使って, datetime.now() が返す日付を固定してしまえば良さそう.
import unittest import mock import datetime import sample_2 ... def test_get_last_month_2(self): with mock.patch('datetime.datetime.now') as now: now.return_value = datetime(2018, 3, 1) self.assertEqual(sample_2.get_last_month(), '201802') ...
こんな風に書きたいんだけど, ビルトインエクステンションの datetime.datetime にはアトリビュートを設定することが出来ない為, 以下のようなエラーとなってしまいました.
$ python -m unittest tests.test_sample_2 -v test_get_last_month_2 (tests.test_sample_2.SampleTest) ... ERROR ====================================================================== ERROR: test_get_last_month_2 (tests.test_sample_2.SampleTest) ---------------------------------------------------------------------- Traceback (most recent call last): File "/patht/to/tests/test_sample_2.py", line 13, in test_get_last_month_2 with mock.patch('datetime.datetime.now') as now: File "/patht/to/.pyenv/versions/3.6.4/lib/python3.6/site-packages/mock/mock.py", line 1460, in __enter__ setattr(self.target, self.attribute, new_attr) TypeError: can't set attributes of built-in/extension type 'datetime.datetime' ---------------------------------------------------------------------- Ran 1 test in 0.003s FAILED (errors=1)
ということで, GitHub – spulec/freezegun: Let your Python tests travel through time というモジュールを利用することで datetime をモック化することが出来ました.
github.com
以下のように @freeze_time() デコレータに固定したい日付を定義するだけ.
import unittest from freezegun import freeze_time import sample_2 class SampleTest(unittest.TestCase): @freeze_time("2018-03-01") def test_get_last_month(self): self.assertEqual(sample_2.get_last_month(), '201802') @freeze_time("2018-04-01") def test_get_last_month(self): self.assertEqual(sample_2.get_last_month(), '201803') @freeze_time("2018-04-01") def test_get_last_month(self): self.assertNotEqual(sample_2.get_last_month(), '201802')
テストを実行してみると…
$ python -m unittest tests.test_sample_2 -v test_get_last_month (tests.test_sample_2.SampleTest) ... ok ---------------------------------------------------------------------- Ran 1 test in 0.295s OK
LGTM.
知見 (3) テスト結果を XML 形式で出力する
メモ
テストの実行結果を JUnit の XML 形式で出力してくれる GitHub – xmlrunner/unittest-xml-reporting: unittest-based test runner with Ant/JUnit like XML reporting. を利用するだけ.
以下のように pip install するだけ.
$ pip install unittest-xml-reporting
テストを実行する際にコマンドラインで以下のように実行するだけです.
$ python -m xmlrunner tests.test_sample_2
以下のように出力されます.
$ python -m xmlrunner tests.test_sample_2 Running tests... ---------------------------------------------------------------------- . ---------------------------------------------------------------------- Ran 1 test in 0.318s OK Generating XML reports...
カレントディレクトリに TEST-tests.test_sample_2.SampleTest-%Y%m%d%H%M%S.xml
というファイルが生成されています
$ cat TEST-tests.test_sample_2.SampleTest-20180330233543.xml <?xml version="1.0" encoding="UTF-8"?> <testsuite errors="0" failures="0" name="tests.test_sample_2.SampleTest-20180330233543" skipped="0" tests="1" time="0.318" timestamp="2018-03-30T23:35:43"> <testcase classname="tests.test_sample_2.SampleTest" name="test_get_last_month" time="0.318" timestamp="2018-03-30T23:35:43"/> <system-out> <![CDATA[]]> </system-out> <system-err> <![CDATA[]]> </system-err> </testsuite>
以上
知見でした.