今川館

都内勤務の地味OLです

テストデータ作成のヒント: 「タプル記法」のすすめ

テストデータをどうやって作りますか?

テストコードを書くとき、テストデータを投入することが多いと思うが、普段皆さんはどうやってテストデータを作っているのだろう。

例えばDjangoは所定のモデルのデータをjsonxml形式で出力して利用するfixtureという仕組みがあるので、これを使うことは多いと思う。
fixture: http://docs.nullpobug.com/django-doc-ja/trunk/topics/testing.html#topics-testing-fixtures

だが、場合によってはこの方法があまり適しないことがある。

fixtureでテストデータを投入すると困る場合

モデル定義を変えた場合にfixtureも直さなければならない

fixtureは静的ファイルにデータを出して利用する仕組みの為、モデル定義を変えてしまうとfixtureのファイルも修正しなければならなくなる。
これが結構面倒くさい。
pythonのコードを修正する方が、xmlファイルやjsonのファイルを修正するより楽である。

テストデータの内容に対する意識が希薄になる

静的ファイルにデータを移してしまうと、データを見るにはわざわざfixtureのファイルを開かなければならないので、テストコードの中にデータ投入処理が書いてある場合に比べてテストデータの内容を気にしなくなる。

例えば検索条件を指定してデータを取得する機能のテストをするときなどはそれでいいのかと思う。

テストデータが見づらい

それと、xmlファイルはそもそも読みにくい。

<?xml version="1.0" encoding="utf-8"?>
<django-objects version="1.0">
    <object pk="1" model="person.person">
        <field type="IntegerField" name="age">13</field>
        <field type="CharField" name="name">easy</field>
        <field type="DateField" name="birthday">2011-01-20</field>
    </object>
</django-objects>

「こんなものを見てどんなデータを投入しているのか理解させるのかよ?」と思わないだろうか。
データの件数を増やすともうxmlファイルを見るのも嫌になってくる。
やっぱりxml形式のデータを目で見るのは苦痛だ。
(jsonxmlにくらべてずっとまし)

pythonのビルトイン関数を使って読み易いテストデータ作成処理を書く

色々考えるとやっぱりテストケースのsetUpでテストデータを投入する処理を書く方が良い。
pythonのコードでテストデータを作りたいと思う。
そんなとき、どんな書き方でテストデータを作っているだろうか。

わたしは「タプルのタプルを使ってスプレッドシートっぽく書く方法」を最近考案して、テストコードを書くときに使っている。
例を示すとこんな感じ。

_d = lambda L: (dict(zip(L[0], x)) for x in L[1:])
datas = ( 
    ("id", "name", "age", "birthday"),
    (1, "easy", 13, date(2011, 1, 20)),
    (2, "bucho", 20, date(2011, 2, 10)),
    (3, "shacho", 22, date(2011, 3, 15)),
    (4, "cto", 18, date(2011, 4, 7)),
)
for data in _d(datas):
    Person.objects.create(**data)

pythonには

  1. 二つのタプルをペアに変換するzip関数
  2. キーと値のペアを辞書に変換するdict関数

という二つのビルトイン関数が存在するので、これを使う。
上記の通り、第1要素にモデルのフィールド名のタプルを、第2要素以降にデータのタプルを書いてループを回してormのデータ生成メソッドを呼ぶなどする。

この方法のいいところは二つある。

データのcreateを何行も書くより見た目がすっきりする

この書き方だと、

Person.objects.create(id=1, name="easy", age=13, birthday=date(2011, 1, 20))
Person.objects.create(id=2, name="bucho", age=20, birthday=date(2011, 2, 10))
Person.objects.create(id=3, name="shacho", age=22, birthday=date(2011, 3, 15))
Person.objects.create(id=4, name="cto", age=18, birthday=date(2011, 4, 7))

こういう風に、何度もcreateを呼ぶ行をコピペするよりずっと読み易い。

簡単、ビルトイン関数だけで作れる

テストデータは見た目がスプレッドシートっぽい方がやっぱり見やすい。
でも、csvexcelのファイルを読み込んでデータを投入する関数を作ったりすると、テキストをパースしたりデータ型を変換したり、テストデータ投入の為にわざわざ面倒なプログラムを書かなければならないし、その処理がバグっているせいでテストが失敗することなどを心配したくない。
そういうのってテスト対象を検証したいのに、テストコードのテストが必要になってしまうような感じがして、何か変な気がする。

しかし、zip, dict, ループはpythonの標準で使える仕組みなので、単純で簡単だ。
コード量も少なくて済む。

これを「タプル記法」 と勝手に名付ける

この書き方を勝手に「タプル記法」と名付けよう。

テストコードもアプリケーションコードと同じように読み易い方がいい。
テストコードの中でデータ投入処理を書いていて、コードが読みにくいと思ったらこの記法を試してみると読み易くなるかもしれない。