frame objectを使ってparametrized test用の関数を作ってみよう

# frame objectを使ってparametrized test用の関数を作ってみよう

(この記事はpython advent calendarに成らなかった記事です。満員でした。)

## paramaterized testとは

パラメータ指定して実行するようなテストのことです。以下の様なことがしたいです。

```python
def add(x, y):
    return x + y

class Tests(unittest.TestCase):
    def _callFUT(self, x, y):
        return add(x, y)

    @paramaterized([
        (1, 2, 3),
        (1, 1, 2),
        (1, 0, 1),
        (2, 3, 4),
        (4, 4, 8)
    ])
    def _test_add(self, x, y, expected):
        """単純な足し算の比較"""
        result = self._callFUT(x, y)
        self.assertEqual(result, expected)
```

実行結果は以下の様な感じになります。_test_addが違うパラメータで５回呼ばれています。

```
...F.
======================================================================
FAIL: test_add_G4 (__main__.Tests)
単純な足し算の比較: args=(2, 3), expected=4
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test-paramaterize.py", line 24, in test
    return method(self, *candidates[n])
  File "test-paramaterize.py", line 52, in _test_add
    self.assertEqual(result, expected)
AssertionError: 5 != 4

----------------------------------------------------------------------
Ran 5 tests in 0.001s

FAILED (failures=1)
```

このようなparameterized関数をどうやって作るかという話です。

## 関数デコレータ

関数デコレータは関数をラップする関数です。ラップすることで関数の挙動を変える事ができます。基本的には関数を返す関数になります。

以下のようなHelloクラスが定義されていた時に。

```python
class Hello(object):
    def __init__(self, name):
        self.name = name

    def hello(self):
        return "hello {name}".format(name=self.name)


Hello("foo").hello() # hello foo
```

以下のようなdoubleデコレータを定義します。

```python
def double(method):
    def _double(self):  # methodなのでselfを取る
        result = method(self)
        return result + result
    return _double
```

doubleは内部のメソッドの結果を２回出力するようなデコレータです。これを先ほどのHelloクラスのhelloメソッドにくっつけてあげるとHello.hello()の挙動が変わります。

```python
class Hello(object):
    def __init__(self, name):
        self.name = name

    @double
    def hello(self):
        return "hello {name}".format(name=self.name)

print(Hello("foo").hello())  # hello foo.hello foo.
```

## unittestのテスト関数

unitttest.TestCaseは「test_」から始まるメソッドをテストメソッドとして認識します。
そのため、parameterizedテストに変換するには、複数のテストメソッドを生成してあげる必要があります。

試しにクラスデコレーターを作ってテストメソッドを増やしてみましょう。
クラスデコレーターはクラスを取りクラスを返します。関数デコレーターのクラス版ですね。

```python
import unittest


def add_foo_test(names):
    def _add_foo_test(cls):
        for name in names:
            def test(self):
                # テキトウに成功するテストを作る
                self.assertEqual(1, 1)
            test.__name__ = "test_{name}".format(name=name)
            setattr(cls, test.__name__, test)
        return cls
    return _add_foo_test


@add_foo_test(["foo", "bar", "boo"])
class Tests(unittest.TestCase):
    pass

unittest.main()
```

add_foo_testはテキトウに成功すテストメソッドを渡された名前のリストを用いて作る関数です。
今回は"foo","bar","boo"が渡されたのでtest_foo,test_bar,test_booというテストメソッドが作られました

結果は以下のようになります。

```
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s

OK
```

３つテストを作っていますね。コレと似たようなことをparameterizedテストで作れば良いのでは？ということになります。

## frame object

しかし、まだ問題がありますね。作りたいparameterized関数はメソッドにデコレートする関数でした。
しかしながら、一つ前の所で作っていたのはクラスをデコレートするデコレーターでした。
このままでは上手くいきません。

メソッドからクラスの値を設定する必要がありそうです。これには実行中のコールスタックを取り出すというようなことをします。

### コールスタック

コールスタックというのは関数の実行中に環境が積まれていくスタックのことです。各呼び出し時点の環境を保持しています。
(これがあるから再帰呼び出ししてもまぁうまくいくわけですね。)

ちょっと例を見てみましょう。f,gという関数があり。gがfを呼び出すというような感じになっています。fはその時点ローカル変数を辞書として返す関数です。

```python
def f(x):
    return locals()  # この環境でのローカル変数を辞書として取り出す。


def g(x):
    r = f(x*x)
    print(locals())
    return r

print(g(10))
# {'r': {'x': 100}, 'x': 10}  # g
# {'x': 100} # f
```

実行してみると、xだけが返ります。

fの中でgのローカル変数を追加してみましょう。呼び出し時には以下の様な形になっているのでコールスタックにアクセスすることでfからgが見えます。

```
[f] <-- current
[g]
[toplevel]
```

どうやって触るかというとsys._getframeという怪しい関数を使います。引数として現在位置から何番目のフレームが欲しいかを渡してあげます。

```
[f] <- sys._getframe() で取得
[g] <- sys._getframe(1) で取得
[toplevel]
```

dummyという変数を追加してみましょう。以下の様にコードを変更します。

```python

import sys


def f(x):
    sys._getframe(1).f_locals["dummy"] = "*dummy*" # 一つ上のフレームのローカル変数にdummyを追加
    return locals()  # この環境でのローカル変数を辞書として取り出す。


def g(x):
    r = f(x*x)
    print(locals())
    return r

print(g(10))  # {'x': 100}
# {'r': {'x': 100}, 'x': 10, 'dummy': '*dummy*'} # g
# {'x': 100} # f

```
このようにfの中でgの中のローカル変数を変えることが出来ました。

## メソッドデコレータの１つ上のフレームのローカル変数がクラスの位置

先のコールスタックに触ることに何の意味があったのでしょう？それは、メソッドデコレーターの１つ上のフレームのローカル変数がクラスの位置だからです。つまり、作ろうとしていたparametrized関数の中でコールスタックを覗くことでメソッドのデコレータ〜としての関数の中から、そのメソッドを持っているクラスにアクセスできるということになります。
例を見せましょう。


```python

import sys


def add_A(method):
    sys._getframe(1).f_locals["A"] = "AAAAAA"
    return method


class Boo(object):
    @add_A
    def hello(self):
        pass

print(Boo.A)  # AAAAAA
```

add_Aというデコレータはメソッドのデコレーターですが。コールスタックを経由してクラスにアクセスできています。Aというクラス変数が設定されています。

## 最後に

今までの知見をまとめましょう。

- メソッドにデコレータをくっつけることでメソッドを変換することができる
- unittestモジュールのテストメソッドは「test_」で始まるメソッド
- コールスタックを経由することで１つ上の呼び出し階層にアクセスできる
- メソッドデコレーターの呼び出し時の位置の１つ上の位置はクラス

これらをまとめてparameterized関数が作れるようになりました。以下が実装です。

```python
import sys

i = 0


def gensym():
    global i
    i += 1
    return "G{}".format(i)


def paramaterized(candidates):
    """candidates = [(args, ..., expected)]"""
    def _parameterize(method):
        env = sys._getframe(1).f_locals
        method_name = method.__name__
        for i, args in enumerate(candidates):
            paramaters = args[:-1]
            expected = args[-1]

            def test(self, n=i):
                return method(self, *candidates[n])

            test.__name__ = "{}_{}".format(method_name.lstrip("_"), gensym())
            doc = method.__doc__ or method_name
            test.__doc__ = "{}: args={}, expected={}".format(doc, paramaters, expected)
            env[test.__name__] = test
        return method
    return _parameterize
```

やりましたね。