箱が…

Amazon箱ストラクチャーが崩れてきそうです。ダンボー作ろうかな。

Ginkgoでテストを書く

みんな大好きgeventをベースにした、daemonを書くためのフレームワークGinkgo(repo)でテストを書く方法について。

Ginkgoについての軽い説明

0.5.0devのドキュメントを読めばわかるんですが、Ginkgo(ギンコ?)は纏まった1つの機能を Service と呼び、Service の中には複数の子Serviceを持てるようになっています。
Service はこんな感じで書きます。

# 1秒毎にコンソールに Hello World と表示し続けるサービス
# http://ginkgo.readthedocs.org/en/latest/user/quickstart.html#hello-world-service からのコピペです
from ginkgo import Service

class HelloWorld(Service):
    def do_start(self):
        self.spawn(self.hello_forever)

    def hello_forever(self):
        while True:
            print "Hello World"
            self.async.sleep(1)

こいつを実行するには、Ginkgoをインストールすると生成されるコマンド ginkgo または ginkgoctl を使います。(上記のコードを hello.py とする)

$ ginkgo hello.HelloWorld

設定ファイル

daemonを作るフレームワークなので、設定ファイルを扱う仕組みが既にあります。
詳しくはドキュメントを読んでいただくとして、下記のコードをみてください。

# service.py
from ginkgo import Service, Setting

class HelloWorld(Service):
    message = Setting("message", default="Hello World",
        help="Message to print out while running")

    def do_start(self):
        self.spawn(self.message_forever)

    def message_forever(self):
        while True:
            print self.get_message()
            self.async.sleep(1)

    def get_message(self):
        return self.message + '!'
# 設定ファイル(といいつつ普通のPythonコード)
# service.conf.py
message = "Services all the way down."
service = "service.HelloWorld"

以下のコマンドで実行

$ ginkgoctl service.conf.py start

上記のコード中の Setting は(ドキュメントに書かれていませんが)シングルトンになっていて、Service が複数あったとしても Setting の第1引数が同じであれば全ての Service (子Serviceも含む)から設定値が参照できます。

で、実は Setting は ginkgo.settings から設定値を取ってくるためのラッパーなので、設定ファイルを介さずに設定値を弄りたいときは ginkgo.settings をつつけばどうとでもなるというわけです。

ここ重要なんで覚えといてください。

テストを書く

ginkgo.settings を直接弄ると「設定ファイルから設定値を読む」処理の下準備をしなくて済むのでとても楽です。

# service_test.py
from unittest import TestCase
from ginkgo import settings

from service import HelloWorld

class HelloWorldTest(TestCase):
    def tearDown(self):
        # .load() で設定値をデフォルトで上書き
        # 与えられた辞書のkeyをもとに settings を更新するので
        # 空の辞書を与えてまっさらに、とはいかない
        settings.load({'message': "Hello World"})

    def test_message(self):
        # 設定値をセット
        settings.load({'message': 'spam'})
        service = HelloWorld()
        assert service.get_message() == 'spam!'

こんな感じで。