6
|
1 \chapter{CppUnit による単体テスト} \label{chapter:unittest}
|
|
2 ここでは一般的なテスト駆動開発のフレームワークの例として CppUnit の紹介と、
|
|
3 過去に CppUnit を用いて行われた Cerium の単体テストについて述べる。
|
|
4
|
|
5 \section{CppUnit}
|
|
6 CppUnit は、xUnit と呼ばれる、単体テストを自動化するテスティング
|
|
7 フレームワークの内の一つで C++ 用に開発されている。
|
|
8 CppUnit の特徴はテストケースを増やすことが容易であり、1 つの事象に対して
|
|
9 様々なテストケースを同時にテストする事ができる。また、羅列したテストケースは
|
|
10 一括で実行、結果の表示ができる。(図\ref{fig:cpptest})
|
|
11
|
|
12 \begin{figure}[h]
|
|
13 \includegraphics[scale=0.6]{images/test_example.pdf}
|
|
14 \caption{CppUnitTest}
|
|
15 \label{fig:cpptest}
|
|
16 \end{figure}
|
|
17
|
|
18 \newpage
|
|
19
|
|
20 \section{CppUnit によるゲームプログラムの単体テスト}
|
|
21 我々は過去に CppUnit を用いてゲームプログラムの単体テストを行なっている。
|
|
22 このテストでは Cerium のオブジェクト管理システムである SceneGraph
|
|
23 (\ref{sec:scenegraph}節) を用い、3 つの SceneGraph ノードを持つオブジェクトの
|
|
24 テストを行った。このオブジェクトは本体の他に左右にパーツを 1 つずつ持つ。
|
|
25 本体を tree の root として左右のパーツがその子供になっている。
|
|
26 (図\ref{fig:boss1})
|
|
27
|
|
28 \begin{figure}[htbp]
|
|
29 \includegraphics[scale=0.8]{images/boss1_SG.pdf}
|
|
30 \caption{boss1}
|
|
31 \label{fig:boss1}
|
|
32 \end{figure}
|
|
33
|
|
34 この boss1 は右に一定速度で移動し、画面上の適当な位置に来ると State パターン
|
|
35 により左方向への移動に状態が遷移する、簡単なゲームの例題となっている。
|
|
36
|
|
37 \begin{verbatim}
|
|
38 static void
|
|
39 boss1_move_right(SceneGraphPtr node, int screen_w, int screen_h) {
|
|
40 node->xyz[0] += node->stack_xyz[0];
|
|
41 if(node->xyz[0] > screen_w-280) {
|
|
42 node->set_move_collision(boss1_move_left, boss1_collision);
|
|
43 }
|
|
44 }
|
|
45
|
|
46 static void
|
|
47 boss1_move_left(SceneGraphPtr node, int screen_w, int screen_h) {
|
|
48 node->xyz[0] -= node->stack_xyz[0];
|
|
49 if(node->xyz[0] < 280) {
|
|
50 node->set_move_collision(boss1_move_right, boss1_collision);
|
|
51 }
|
|
52 }
|
|
53 \end{verbatim}
|
|
54
|
|
55 このテストでは root のアドレスを取得し、そこから tree を辿って
|
|
56 各オブジェクトの座標を取得し、その初期化が正しいか、状態遷移において正しい
|
|
57 値を保持しているか調べた。
|
|
58
|
|
59 \begin{verbatim}
|
|
60 void
|
|
61 sgTest::rootTest() {
|
|
62 test_init();
|
|
63
|
|
64 sg_root->print_member();
|
|
65 CPPUNIT_ASSERT_EQUAL((float)width/2, sg_root->xyz[0]);
|
|
66 CPPUNIT_ASSERT_EQUAL(0.0f, sg_root->xyz[1]);
|
|
67 CPPUNIT_ASSERT_EQUAL(-100.0f, sg_root->xyz[2]);
|
|
68 }
|
|
69
|
|
70 void
|
|
71 sgTest::childTest() {
|
|
72 while (sg_root) {
|
|
73 if(sg_root->children != NULL) {
|
|
74 sg_root->children->print_member();
|
|
75 ...
|
|
76 sg_root = sg_root->children;
|
|
77 } else if(sg_root->brother != NULL) {
|
|
78 sg_root->brother->print_member();
|
|
79 CPPUNIT_ASSERT_EQUAL(0.0f, sg_root->brother->xyz[0]);
|
|
80 ...
|
|
81 sg_root = sg_root->brother;
|
|
82 } else {
|
|
83 ...
|
|
84 \end{verbatim}
|
|
85
|
|
86 このテストの結果、全てのオブジェクトの初期位置と状態遷移した値が正しいことが
|
|
87 分かった。しかし、ここで行った単体テストはゲームにおける、ある一瞬の値の正誤
|
|
88 しか調べることができない。ゲームプログラムは時間の経過と共にオブジェクトの
|
|
89 パラメータが常に変化するため、こうした一般的な単体テストではゲームのバグを
|
|
90 発見するのは難しい。
|