Python で書く代数 - pytest 編

テストについて

前回は群を Python で書いてみました. 群は公理というルールを使って定義されていました. この公理を Python で表現するのに, テストが使われていました.

Python でのテストと言えば unittest や nose が有名ですが, pytest という新しいテストライブラリのことを知ったので, これを使ってみました.

この pytest の大きな特徴はテストメソッドで使用する値を簡単にパラメータ化できることです.

人間がテストケースを考える場面では, ただ闇雲に適当な値を渡すのはテストとしては良いものではありません. とりあえずで値を列挙すると無駄な意味の無いテストケースを作ってしまうことが多く, テストの実行自体に時間がかかります. そしてエラーが起きたとしても, そのエラーがいったいどんな原因によって起きたのか考えないといけません. (個人的にはあまり頭を使わず安直にエイヤというのは好きではないです.)

ランダムな値を渡す手法は fuzzing と呼ばれていて, 人間がテストケースを想定する範囲の外にあるバグを見付けるのに使われるそうです. 詳しくはこのスライド http://www.slideshare.net/TokorotenNakayama/fuzzing-pyfes を見ると良いと思います.

群の公理は「群の任意の元」という表現を含んでいるため, 具体値を使用するテストでは無限の時間が必要になってしまい, 全てをテストすることは不可能です. なので, 今回の Group クラスのサブクラスのテストとしては, パラメータ化されたテストで特殊値の付近だけをテストすることにしました. 例えば, Integer クラスでは整数を表現しているので特殊値として 0 があります. 0 の付近の値でテストが通れば, 高い確率で他の値でも動くものと期待できます.

pytest によるテスト

pytest は easy_install もしくは pip コマンドでインストールできます. ここでは virtualenvwrapper を使って, pytest という名前の個別の環境にインストールしています.

pip, virtualenv, virtualenvwrapper についてはこの記事 http://d.hatena.ne.jp/rudi/20110107/1294409385 に詳しく載っています. そちらを参照してください.

$ echo $VIRTUALENV_USE_DISTRIBUTE
True
$ mkvirtualenv pytest
(pytest)$ pip install pytest

テストケースの実行は pytest コマンドで行います.

$ pytest tests/grouptest.py

pytest にテストケースとして認識させるには, メソッドの先頭を test にすれば良いようです. ここは nose などと仕様を合わせているようです. (ドキュメントには明記されていなかった気がしますが.)

肝心のテスト関数にパラメータを渡すところはデコレータを使用して以下のように書きます.

@pytest.mark.parametrize('cls', groups)
def test_ZERO(cls):
    cls.ZERO
    return True

pytest.mark.parametrize の第1引数にはテスト関数の引数の名前を, 第2引数にはテスト関数に渡す値の list を入れます. この list の部分を generator で書いてしまうとエラーが出て, テストが実行できません. どうやら pytest が最初にテストケースをカウントしていて, 長さが測れるものでないといけないようです. 最初エラーメッセージの意味が分からず, 長いことハマっていました.

複数の引数を持つテスト関数を書く場合は以下のようにします.

integer_values = [Integer(i) for i in range(-2, 3)]

@pytest.mark.parametrize(
    ('a', 'b'),
    [(a, b) for a in integer_values for b in integer_values]
)
def test_equals_symmetric_law(a, b):
    if a == b:
        assert b == a

第1引数が tuple になり, パラメータの値も tuple になっています.

今回使用したテストコードの完全なソースコードは https://bitbucket.org/cocoatomo/pyarith/changeset/08e381828aeb#chg-tests/grouptest.py を参照してください.

過去のリビジョンの URL を張っているのは, 実は現在は別の形式にしているからです. その話はまた次の記事で書こうと思います.

まとめ

この記事では pytest を使ってテスト関数の引数をパラメータ化し, そこに入れる値をリストとして書きました. これによって同じテスト関数を色々な値で実行できるようになりました.

pytest にはまだまだ機能があるので, 興味がある方は本家のドキュメントを読んでみてください.

Python で書く代数

代数は数学の中でもプログラミング言語と近しい存在です. 歴史的な事実関係は逆でプログラミング言語が抽象代数的な発想の中から生まれてきた, と言えるでしょう.

この記事では代数学上の事柄について, プログラミング言語の Python を使って記述していきます.

代数

現代の抽象的な代数は公理による定義を行います. 「公理」というのはひらたく言うとルールのことで, そのルールから証明という手続きを経て, 命題や定理を導き出します.

公理では

  • あるべき対象
  • あるべき演算
  • 満たすべき性質

について記述します.

プログラミングで言えば

  • あるべきオブジェクトや持っているべきプロパティ
  • あるべきメソッドやオペレータ
  • オブジェクトやメソッドが持つべき性質

にあたり, これらはテストでチェックしたり, Java の interface および abstract class 相当の仕組みを利用して, 実装しなければならないメソッドを表現します.

この対応関係を着想として, まずは代数的対象のうち「群 (group)」を Python で表現してみます.

ここで載せるソースコードは全て https://bitbucket.org/cocoatomo/pyarith にあります. 順次アップデートしていきます.

群を扱った Python スクリプトは https://bitbucket.org/cocoatomo/pyarith/src/1b3a578e1678/group.py です. テストスクリプトは https://bitbucket.org/cocoatomo/pyarith/src/1b3a578e1678/tests/grouptest.py です.

クラス Group を継承したクラスが群となり, そのクラスのインスタンスが群の元に相当します.

abstract method として実装してある __eq__, __add__, __neg__ メソッドが, 群であるために必須のメソッドとなります. a = b は実は a.__eq__(b) のことであり, a + b, -a もそれぞれ a.__add__(b), a.__neg__() に相当します. またテストの方でその存在をチェックしている ZERO というフィールドも, それが存在することが群に課せられた条件です. つまり, 群には「等号比較」「加法」「零元」「逆元」が必須の条件なのです. (等号の性質に関しては通常は群の公理に含めません. ここでは等号も実装しているので, 等号の性質も一緒にテストしています.)

これらは好き勝手に実装して良いのではなく, 満たすべき制約条件があります. このテストスクリプトでは, それをテスト関数の形で表現しています. (演算によって群が閉じていることのテストが抜けていますね…… 追加しておきます.)

テストケースとその意味
test_equals_reflexive_law 等号の反射律
test_equals_symmetric_law 等号の対称律
test_equals_transitive_law 等号の推移律
test_add_ZERO 零元の性質
test_add_negative 逆元の性質
test_add_commutative_law 加法の可換性
test_add_associative_law 加法の結合律

それぞれの制約がどういうものかはスクリプトを見ればすぐに分かると思います. 群の公理 (ルール) はこれで全てです. http://ja.wikipedia.org/wiki/%E7%BE%A4_(%E6%95%B0%E5%AD%A6) と見比べて確認してみてください.

(可換性は全ての群が備えているものとは限りません. 可換性を持つ群を「可換群」, 可換性を持たない群を「非可換群」と呼びます.)

まとめ

この記事では, 群の公理を abstract method とテスト関数を使って記述しました. Python スクリプトとして記述することで少しだけ理解しやすくなれば嬉しく思います.

これだけの少ないルールから群の様々な性質が証明されていくのは不思議ですね.

次回は環や体を対象としたり, テストを行うのに使用した pytest モジュールについて書こうと思います.

それでは.

Licenses