tachitomonn’s blog

IT技術関連の学習メモがメインでたまに趣味のこととか

Python でテスト unittest

Python の標準ライブラリに含まれるユニットテストフレームワーク unittest の基本を押さえます。
公式ドキュメント
unittest --- ユニットテストフレームワーク — Python 3.8.1 ドキュメント
によれば、

unittest ユニットテストフレームワークは元々 JUnit に触発されたもので、 他の言語の主要なユニットテストフレームワークと同じような感じです。 テストの自動化、テスト用のセットアップやシャットダウンのコードの共有、テストのコレクション化、そして報告フレームワークからのテストの独立性をサポートしています。


公式ドキュメントの基本的な例を参考に前回の記事の平成暦を西暦に変換する関数を unittest でテストするスクリプトを書くとこんな感じ。

study_unittest.py

#! /usr/bin/env python

u"""unitest の勉強用サンプル
"""

import unittest
from study_doctest import heisei2seireki

class TestSample(unittest.TestCase):

    def test_heisei2seireki(self):
        self.assertEqual([heisei2seireki(n) for n in range(1, 32)],
                [1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019])
        with self.assertRaises(ValueError):
            heisei2seireki(-1)
        with self.assertRaises(ValueError):
            heisei2seireki(0)
        with self.assertRaises(ValueError):
            heisei2seireki(1.5)
        with self.assertRaises(ValueError):
            heisei2seireki(32)

if __name__=="__main__":
    unittest.main()

ポイントとしては、

  • テストケースは、 unittest.TestCase のサブクラスとして作成する
  • テスト対象のメソッド名は test で始めることにより、テストランナーがこの命名規約によってテストを行うメソッドを検索してくれる
  • assert 文の代わりに assert~ メソッドを使用すると、テストランナーでテスト結果を集計してレポートを作成する事ができるらしい

最後の unittest.main() は、テストスクリプトコマンドライン用インターフェースを提供していてコマンドラインからこのスクリプトを実行すると、以下のように出力される。

>python study_unittest.py
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

doctest と同様に -v オプションを付けて実行すると詳細なログを出力してくれる。

>python study_unittest.py -v
test_heisei2seireki (__main__.TestSample) ... ok

----------------------------------------------------------------------
Ran 1 test in 0.002s

OK

またこちらの記事がわかりやすかったので参考にさせていただきました。
[Python] ユニットテストをPythonで書く - YoheiM .NET

Python でお手軽テスト doctest

公式ドキュメントを参考に doctest のお勉強。
doctest --- 対話的な実行例をテストする — Python 3.8.1 ドキュメント

doctest モジュールは、対話的 Python セッションのように見えるテキストを探し出し、セッションの内容を実行して、そこに書かれている通りに振舞うかを調べます。

平成暦を西暦に変換する関数(単に試しの例が他に思いつかなかっただけで特に意味なし)を書いて試してみた。

study_doctest.py

#! /usr/bin/env python

u"""doctest の勉強用サンプル
"""

def heisei2seireki(n):
    u"""平成暦を西暦に変換
    >>> [heisei2seireki(n) for n in range(1, 32)]
    [1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019]
    >>> heisei2seireki(-1)
    Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
        File "c:\selfstudy\study_doctest.py", line 12, in heisei2seireki
        raise ValueError(u"n は1以上!")
    ValueError: n は1以上!
    >>> heisei2seireki(0)
    Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
        File "c:\selfstudy\study_doctest.py", line 12, in heisei2seireki
        raise ValueError(u"n は1以上!")
    ValueError: n は1以上!
    >>> heisei2seireki(1.5)
    Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
        File "c:\selfstudy\study_doctest.py", line 14, in heisei2seireki
        raise ValueError(u"n は整数!")
    ValueError: n は整数!
    >>> heisei2seireki(32)
    Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
        File "c:\selfstudy\study_doctest.py", line 16, in heisei2seireki
        raise ValueError(u"n の最大値は31!")
    ValueError: n の最大値は31!
    """

    import math
    if not n > 0:
        raise ValueError(u"n は1以上!")
    if math.floor(n) != n:
        raise ValueError(u"n は整数!")
    if n > 31:
        raise ValueError(u"n の最大値は31!")
    result = n + 1988
    return result

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

このスクリプトコマンドラインから実行すると出力は何もないが、すべての実行例が正しく動作しているからとのこと。
-v オプションを付けて実行すると何を実行しようとしたかを記録したログを出力し、最後にまとめを出力してくれる。

>python study_doctest.py -v
Trying:
    [heisei2seireki(n) for n in range(1, 32)]
Expecting:
    [1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019]
ok
Trying:
    heisei2seireki(-1)
Expecting:
    Traceback (most recent call last):
        File "", line 1, in 
        File "c:\selfstudy\study_doctest.py", line 12, in heisei2seireki
        raise ValueError(u"n は1以上!")
    ValueError: n は1以上!
ok
Trying:
    heisei2seireki(0)
Expecting:
    Traceback (most recent call last):
        File "", line 1, in 
        File "c:\selfstudy\study_doctest.py", line 12, in heisei2seireki
        raise ValueError(u"n は1以上!")
    ValueError: n は1以上!
ok
Trying:
    heisei2seireki(1.5)
Expecting:
    Traceback (most recent call last):
        File "", line 1, in 
        File "c:\selfstudy\study_doctest.py", line 14, in heisei2seireki
        raise ValueError(u"n は整数!")
    ValueError: n は整数!
ok
Trying:
    heisei2seireki(32)
Expecting:
    Traceback (most recent call last):
        File "", line 1, in 
        File "c:\selfstudy\study_doctest.py", line 16, in heisei2seireki
        raise ValueError(u"n の最大値は31!")
    ValueError: n の最大値は31!
ok
1 items had no tests:
    __main__
1 items passed all tests:
   5 tests in __main__.heisei2seireki
5 tests in 2 items.
5 passed and 0 failed.
Test passed.

個人の趣味の範囲での開発におけるテストならこれでも良いかも。

Python でお手軽テスト assert 文

assert 文についてのメモ書き。
assert 文を使うと条件が True でないときに例外を投げてくれる。
想定と違ったらとりあえず止めるみたいな処理を手軽に仕込めるので、走り書きのスクリプトとかの動作確認なんかには重宝しそう。
ちなみに条件が True だと何もしない。
書き方は

assert 条件式, 条件式がfalseのときに出力するメッセージ

こんな感じ。

>>> assert 1 == 1
>>> assert 1 == 2
Traceback (most recent call last):
  File "", line 1, in 
AssertionError

Python 製ドキュメント作成ツール sphinx に触れてみる

前回の記事で reStructuredText も体験したので今回はいよいよ sphinx を体験してみます。
今回、参考にさせていただいたのはこちらです。
sphinx でドキュメント作成からWeb公開までをやってみた - Qiita

何はともあれまずはインストール。
特定の開発環境に依存して使うわけではないので仮想環境ではなく直接インストールします。

py -3 -m pip install sphinx

今回はバージョン 2.0.1 がインストールされました。

sphinx ではドキュメントをプロジェクトという括りで管理していく。
プロジェクトの作成の前にプロジェクトの ROOT ディレクトリを作成します。
作成した ROOT ディレクトリに移動してプロジェクトの作成コマンドを実行します。

sphinx-quickstart

諸々の質問には基本的にデフォルトのままで以下だけ設定。

> Separate source and build directories (y/n) [n]: y
> Project name: sphinx test
> Author name(s): tachitomonn
> Project release []:
> Project language [en]: ja

これでプロジェクトは作成されたけど HTML などの実際のドキュメントはまだ作成されていない。
make html コマンドで HTML を作成することができる。

make html

すると ROOT ディレクトリの build 以下に作成される。
ROOT ディレクトリの source 内の index.rst を元にして HTML が作成されるので、 source 以下に reST ファイルを作成し、 make html コマンドで HTML を作成していくとのこと。

デザインテーマの変更は conf.py の html_theme の値を変更すればよいとのこと。
変更後、 make html コマンドを実行することでデザインが変更される。

ページを追加してみる。
例えば本のレビューを各本ごとにページとして追加していくなら、 source 下に review ディレクトリを作成。
review ディレクトリ下に各本の rst を作成(明示的に utf-8 でファイルエンコーディング)していく。
続いて index.rst を編集(明示的に utf-8 でファイルエンコーディング)して目次を作成する。
make html コマンドでビルドという流れになる。

Python で reStructuredText

Python でドキュメント作成といえば sphinxsphinx といえば reStructuredText ということで今回は reStructuredText の基礎に触れてみます。

参考にしたのはリファレンス替わりにも使えるかなとも思ったのでこちら。
reStructuredText入門 — Sphinx 1.5.6 ドキュメント

最低限、セクション構造・リスト・インラインマークアップリテラルブロック・リンクあたりを抑えておけばとりあえず体裁の整ったドキュメントを書くことができそうなので何はともあれやってみました。
書いてみたテキストはこんなやつ。

test.rst

==========
reSTの勉強
==========

段落(パラグラフ)
==================

ここから一段落です。
ここは *強調* です。
ここは **強い強調** です。
ここは ``コードサンプル`` です。
ここまで一段落です。

リスト
======

リスト1
--------

* りんご
* ゴリラ
* ラッパ

リスト2
--------

1. ビール

   1. ラガー
   2. エール

2. ウイスキー

.. _list3:

リスト3
--------

#. 野球
#. サッカー

リテラルコードブロック
======================

以下はコードサンプル ::

    print("code sample")

ここからは通常の段落です。

外部リンク
==========

`グーグルへのリンク`_

.. _`グーグルへのリンク`: https://www.google.com/

内部リンク
==========

リスト3へ戻る list3_

`リスト`_ へ戻る

このファイルを Python で html ファイルへ変換してみる。
変換には docutils ライブラリを使う。
作業用の仮想環境を作ります。
コマンドプロンプトから

py -3 -m venv selfstudy

作成した仮想環境に docutils を入れる。

pip install docutils

変換スクリプトが使えるようになるのでファイルを指定して実行してみる。

rst2html.py C:\resttest\test.rst > C:\resttest\test.html

test.html をブラウザで開くときちんと表示されました。

Python の仮想環境

2019年版 Python 開発環境を整える - tachitomonn’s blog
にて構築した環境に python の仮想実行環境を整えます。

2019年春の時点では python 2系では virtualenv 、3系では venv を使用するのがスタンダードっぽいです。
今回はこちらを参考にさせていただきました。
複数バージョンのPython2系と3系をvenv(virtualenv)で切り替えて使用する環境構築まとめ(windows編) | ぱーくん plus idea

2系に対して pip で virtualenv を入れます。コマンドプロンプトから

py -2 -m pip install virtualenv

を実行。
仮想環境の作成はコマンドプロンプトから

py -2 -m virtualenv <仮想環境名>

でカレントディレクトリに仮想環境を作成できる。
作成した仮想環境に入るときは

<仮想環境名>\Scripts\activate.bat

を実行、仮想環境から抜けるには

<仮想環境名>\Scripts\deactivate.bat

を実行する。

3系での venv は Python 3.3 から標準機能となっているので今回の環境では改めてイントールの必要はない。
virtualenvと同様に

py -3 -m venv <仮想環境名>

で仮想環境の作成、

<仮想環境名>\Scripts\activate.bat

で仮想環境に入り、

<仮想環境名>\Scripts\deactivate.bat

で仮想環境から抜けることができる。

Python の enumerate関数

Python の enumerate関数を使うとループ中でシーケンスのインデックスと値を取得できる。
覚えていたら今までに絶対使う機会あっただろうなと思うので忘れないよう書いとく。
こんな感じ。

In [5]: seq1 = ["a", "b", "c", "d"]

In [6]: for idx, val in enumerate(seq1):
   ...:     print(idx, val)
   ...:
0 a
1 b
2 c
3 d

In [7]: seq2 = "abcd"

In [8]: for idx, val in enumerate(seq2):
   ...:     print(idx, val)
   ...:
   ...:
0 a
1 b
2 c
3 d