OMake/C++でPython unittestを使ってスマートにテストしたい
つまりはTestをC++でかくのはメンドクセーから、どうせBOOST_PYTHON_MODULE(hoge)でPython用のインターフェースを公開しているわけだし、PythonでTest書いたらステキーなんじゃないかと思いましたっていう単純な話です。
シェルから起動したようなPythonからimport hogeするには、soの作成やらめんどくさい紆余曲折が必要らしいので、hogeを公開してる実行形式の引数としてファイルが与えられたらそれを内部で評価するという形式で。
>main.cpp
using namespace boost::python; BOOST_PYTHON_MODULE(hoge) { // hogehoge } void python_initialize(int argc, char** argv) { if(PyImport_AppendInittab("hoge", inithoge) == -1) throw std::runtime_error("python_initialize: import error"); Py_Initialize(); if(argc > 0) PySys_SetArgv(argc -1, &argv[1]); } void python_finalize() { //Py_Finalize(); } int main(int argc, char** argv) { python_initialize(argc, argv); int rc = 0; if(argc > 1) { rc= exec_test(argc, argv); } python_finalize(); return rc; } int exec_test(int argc, char** argv) { try { PyObject * pyfile = PyFile_FromString(argv[1], "r"); if(!pyfile) return 404; PyRun_SimpleFile(PyFile_AsFile(pyfile), argv[1]); Py_XDECREF(pyfile); } catch (error_already_set) { PyErr_Print(); } return 0; }
Pythonをまともに使うのなんてDjangoいじってた時以来だなーなんて感慨に耽ったりしつつも、Python-unittestの使い方なんぞは忘却の彼方にあるため、unittestの書き方 - Λάδι Βιώσαςを参考にさせていただきつつ、適当に書きます。
>tests/test.py
import unittest import hoge class BasicTest(unittest.TestCase): def testTest(self): print "Test!" def suite(): s = unittest.TestSuite() s.addTest(unittest.makeSuite(BasicTest)) return s if __name__ == "__main__": unittest.main()
イェー
% ./build/hoge tests/test.py BasicTest.testTest Test! . ---------------------------------------------------------------------- Ran 1 test in 0.000s OK %
さて、ここからが本題。
Testが起動できたなら、omake -P&を常用する手前、やはりPythonのTestも自動で実行したいもの。
あるTestを編集したら、編集したTestだけ実行できたらいいよね等々、人間というのはやはり、どうやら欲張りなようです。
OMakefileというmakefile界のニッチ市場(・・・か?)で、顔を真っ赤にしつつTry&Errorを繰り返します。
>tests/OMakefile
pytests = $(glob ./*.py) dummy_logs = $(addsuffix .log, $(removesuffix $(pytests))) foreach(i, $(dummy_logs)): .PHONY: $(i) %.log: %.py $(BUILD_DIR)/hoge$(EXE) $(BUILD_DIR)/hoge$(EXE) $< .PHONY: runcxxtests runcxxtests: test$(EXE) ./test$(EXE) .PHONY: runtests runtests: runcxxtests $(dummy_logs) test$(EXE): $(BUILD_DIR)/libhoge$(EXT_LIB) LIBS = $(BUILD_DIR)/libhoge .DEFAULT: runtests $(CXXProgram test$(EXE), $(removesuffix $(glob *.cpp)))
キモは、%.log: %.pyという抽象ルールを使ってテストコードを起動している点。
runpytests: $(pytests)のように、複数のpyファイルに依存するルールを定義してというやり方を最初に考えましたが、「複数のpyファイルの中でどれが更新されたのか」をお手軽に取得する方法が思いつきませんでした。無念。
%.py: %.pyみたいに書ければ落なんですが・・・。
.PHONY: $(i)に関しては、ここで[test1.log: test1.py ...]に.PHONY指定をしておかないと、[test1.log...]が最低限、存在している必要が出てきます。
存在しなければその時点でコンパイルエラーとして判断されて悲しい事になってしまいます。
というわけで、テストのテスト。
% echo >> tests/test.py *** omake: file tests/test.py changed *** omake: rebuilding - build tests <test.log> + ../build/hoge test.py .[============================================================ ] 01327 / 01331 ---------------------------------------------------------------------- Ran 1 test in 0.000s OK Test! *** omake: done (0.04 sec, 0/42 scans, 15/60 rules, 25/3609 digests) *** omake: polling for filesystem changes
C++テストコードの編集。pyファイルに記述したテストは起動しないでほしい。
% echo >> tests/test.cpp *** omake: file tests/test.cpp changed *** omake: rebuilding - build tests test.o + g++ -pipe -std=c++0x ... - build tests <runcxxtests> + ./test Running 15 test cases... 20 *** No errors detected *** omake: done (3.22 sec, 5/47 scans, 25/73 rules, 26/3629 digests) *** omake: polling for filesystem changes
ライブラリを構成するファイルの更新。
全部のテストをやってほしい。
% echo >> src/hoge.cpp *** omake: file src/hoge.cpp changed *** omake: rebuilding - build tests <test.log> + ../build/hoge test.py .[=========================================================== ] 01323 / 01331 ---------------------------------------------------------------------- Ran 1 test in 0.000s OK Test! - build tests <runcxxtests> + ./test Running 15 test cases... 20 *** No errors detected *** omake: done (2.19 sec, 6/48 scans, 32/80 rules, 32/3644 digests) *** omake: polling for filesystem changes
ここに来て気づくのが、テストに失敗したときは失敗した時点で止まって欲しいもの。
となると、%.logの.PHONY指定はむしろ邪魔でえーーーと
な、涙目・・・。