c言語テストフレームワー クunityを使ってみよう
TRANSCRIPT
ソフトウェアテストとは
コンピュータ・コードが意図されたように動作し意図されないことはすべて実行しないように設計されていることを検証するように設計されたプロセス,あるいは,一連のプロセスである.
G. J. Meyers,ソフトウェア・テストの技法第2版より
テスト駆動開発
•実装を行うより先に,テストを作成する.実装は,そのテストをパスすることを目標に行う.
• アジャイル開発手法のエクストリーム・プログラミングの19あるプラクティスのうちの一つ.
• テスト駆動開発を行うためには,テスト実行の自動化が必要.
xUnit
• コンピュータプログラムの単体テストを行うためのテスティングフレームワークの総称
•事前に定義されたテストケースを自動的に実行する
•様々な言語に対して開発されている• https://ja.wikipedia.org/wiki/XUnit
実際に触ってみよう…の前に
•下記のコマンドでソースコード一式をとってきてください.
wget “http://exa.dbms.cs.kumamoto-u.ac.jp/~y-manabe/class/jikken2_5B/2018/files/sample_proj.tar.gz”
•解凍してください• tar xvf ./sample_proj.tar.gz
ソースコードの構成
• sample_proj/• src/• test/
• testcases/• testrunner/
• mocks/• Unity-master/• CMock-master/• CMakeList.txt
1.ソースコードを書く
<src/main.c(テストでは使わない)>#include "succ.h"#include <stdio.h>
int main (int argc, char *argv[]){int i;i=atoi(argv[1]);i=succ(i);printf("%d¥n",i);
}
<src/succ.c>#include <stdio.h>#include "succ.h"
int succ (int i){return i+1;
}
<src/succ.h>int succ (int i);
2.実装に対するテストケースを書く<test/testcase/test_succ.c>#include <unity.h>#include <unity_fixture.h>#include "succ.h"
TEST_GROUP(Succ);
TEST_SETUP(Succ){}TEST_TEAR_DOWN(Succ){}
TEST(Succ, succ_sample_test){TEST_ASSERT_EQUAL_INT(31, succ(30));
}
テストグループの定義
テスト実行前に行う処理の定義
テスト実行後に行う処理の定義
テスト内容
(注)TEST_ASSERT_*な関数はたくさんある.Unity-master/docs/UnityAssertionsReference.mdを参照のこと.
3.テストケースを実行するテストランナーを作成する<test/testrunner/succ_test_runner.c>#include <unity.h>#include <unity_fixture.h>
TEST_GROUP_RUNNER(Succ){RUN_TEST_CASE(Succ, succ_sample_test);
}
テスト実行の定義
引数は(テストグループ,テスト名)
4.すべてのテストケースを実行するテストランナーを作成する<test/testrunner/all_tests.c>#include <unity_fixture.h>
static void RunAllTests(void){
RUN_TEST_GROUP( Succ );}
int main( int argc, char const **argv ){
return UnityMain( argc, argv, RunAllTests );}
テストグループ実行の定義
引数は(テストグループ,テスト名)
5.CMakefileList.txtを作る<CMakeList.txt>cmake_minimum_required( VERSION 2.8 )project( unity_test )
set( UNITY_ROOT Unity-master )
include_directories( ${UNITY_ROOT}/src )include_directories( ${UNITY_ROOT}/lib )include_directories( ${UNITY_ROOT}/extras/fixture/src )include_directories( src/ )link_libraries( m )
set(UNITY_FILES
${UNITY_ROOT}/src/unity.c${UNITY_ROOT}/extras/fixture/src/unity_fixture.c
)
set(TEST_FILES
test/testcase/test_succ.c)
set(SOURCE_FILES
src/succ.c)
set(TEST_RUNNER_FILES
test/testrunner/succ_test_runner.ctest/testrunner/all_tests.c
)
add_executable( unity_test ${SOURCE_FILES} ${TEST_FILES} ${TEST_RUNNER_FILES} ${UNITY_FILES} )
5.CMakefileList.txtを作る<CMakeList.txt>cmake_minimum_required( VERSION 2.8 )project( unity_test )
set( UNITY_ROOT Unity-master )
include_directories( ${UNITY_ROOT}/src )include_directories( ${UNITY_ROOT}/lib )include_directories( ${UNITY_ROOT}/extras/fixture/src )include_directories( src/ )link_libraries( m )
set(UNITY_FILES
${UNITY_ROOT}/src/unity.c${UNITY_ROOT}/extras/fixture/src/unity_fixture.c
)
set(TEST_FILES
test/testcase/test_succ.c)
set(SOURCE_FILES
src/succ.c)
set(TEST_RUNNER_FILES
test/testrunner/succ_test_runner.ctest/testrunner/all_tests.c
)
add_executable( unity_test ${SOURCE_FILES} ${TEST_FILES} ${TEST_RUNNER_FILES} ${UNITY_FILES} )
テストケースファイルのリスト
ソースファイルのリスト
テストランナーのリスト
こうなる
y-manabe@st110:~/sandbox/unity_practice/sample_proj_bak$ ./unity_testUnity test run 1 of 1.
-----------------------1 Tests 0 Failures 0 IgnoredOK
課題1
• test/testcase/test_succ.cと,test/testrunner/succ_test_runner.cにコードを追加して,succ関数に対する新しいテストを書いてみよう
解答例<test/testcase/test_succ.c>#include <unity.h>#include <unity_fixture.h>#include "succ.h"
TEST_GROUP(Succ);
TEST_SETUP(Succ){}TEST_TEAR_DOWN(Succ){}
TEST(Succ, succ_sample_test){TEST_ASSERT_EQUAL_INT(31, succ(30));
}
TEST(Succ, succ_sample_test2){TEST_ASSERT_EQUAL_INT(1, succ(0));
}
<test/testrunner/succ_test_runner.c>#include <unity.h>#include <unity_fixture.h>
TEST_GROUP_RUNNER(Succ){RUN_TEST_CASE(Succ, succ_sample_test);RUN_TEST_CASE(Succ, succ_sample_test2);
}
課題2
• 2つの引数の積を返す関数multiをmulti.cとmulti.hとして作成し,テストを作成せよ.
<src/multi.c>#include <stdio.h>#include "multi.h"
int multi (int i, int j ){return i*j;
}
<src/multi.h>int multi (int i, int j);
解答例<test/testcase/test_multi.c>#include <unity.h>#include <unity_fixture.h>#include "multi.h"
TEST_GROUP(Multi);
TEST_SETUP(Multi){}TEST_TEAR_DOWN(Multi){}
TEST(Multi, multi_sample_test){TEST_ASSERT_EQUAL_INT(6, multi(2,3));
}
<test/testrunner/multi_test_runner.c>#include <unity.h>#include <unity_fixture.h>
TEST_GROUP_RUNNER(Multi){RUN_TEST_CASE(Multi, multi_sample_test);
}
<test/testrunner/all_tests.c>#include <unity_fixture.h>
static void RunAllTests(void){
RUN_TEST_GROUP( Succ );RUN_TEST_GROUP(Multi);
}
int main( int argc, char const **argv ){
return UnityMain( argc, argv, RunAllTests );}
5.CMakefileList.txtを作る<CMakeList.txt>cmake_minimum_required( VERSION 2.8 )project( unity_test )
set( UNITY_ROOT Unity-master )
include_directories( ${UNITY_ROOT}/src )include_directories( ${UNITY_ROOT}/lib )include_directories( ${UNITY_ROOT}/extras/fixture/src )include_directories( src/ )link_libraries( m )
set(UNITY_FILES
${UNITY_ROOT}/src/unity.c${UNITY_ROOT}/extras/fixture/src/unity_fixture.c
)
set(TEST_FILES
test/testcase/test_succ.ctest/testcase/test_multi.c
)
set(SOURCE_FILES
src/succ.csrc/multi.c
)
set(TEST_RUNNER_FILES
test/testrunner/succ_test_runner.ctest/testrunner/multi_test_runner.ctest/testrunner/all_tests.c
)
add_executable( unity_test ${SOURCE_FILES} ${TEST_FILES} ${TEST_RUNNER_FILES} ${UNITY_FILES} )
単体テストの問題点
• ある関数をテストするために実行するには,その関数が依存する関数をすべて実装していないといけない
関数A 関数B 関数C呼び出し 呼び出し
関数Aを実行するためには,関数BとCが必要→関数Aの単体テストができない!
モックの例
引数から1引いた数を与える関数
<完成形>#include <stdio.h>#include "dec.h"
int dec(int num){return num-1;
}
<モック>#include <stdio.h>#include "dec.h"
int dec(int num){if (num==3){
return 2;}return 0;
}
1.ソースコードを書く<src/getDay.c>#include <stdio.h>#include "getDay.h"#include "getMonth.h"
int getDay(int day, int month){return getMonth(month)+day;
}
<src/getDay.h>int getDay(int day, int month);
<src/getMonth.h>int getMonth(int month);
<src/getMonth.c>未作成.モックを作る対象のコード.
2.テストケースを書く<test/testcase/test_getDay.c>#include <unity.h>#include <unity_fixture.h>#include "getDay.h"#include "MockgetMonth.h"
TEST_GROUP(getDay);
TEST_SETUP(getDay){
getMonth_ExpectAndReturn(12,324);}
TEST_TEAR_DOWN(getDay){}
TEST(getDay, getDay_mock_sample_test){TEST_ASSERT_EQUAL_INT(365,
getDay(31,12));}
(注1)getMonth.hでなく,MockgetMonth.hをインクルードする
(注2)引数に対して,想定する返り値を設定する関数
3.テストランナーを書く
<test/testrunner/getDay_test_runner.c>#include <unity.h>#include <unity_fixture.h>
TEST_GROUP_RUNNER(getDay){RUN_TEST_CASE(getDay,
getDay_mock_sample_test);}
4テスト全体のテストランナーを変更する
<test/testrunner/all_tests.c>#include <unity_fixture.h>
static void RunAllTests(void){
RUN_TEST_GROUP( Succ );RUN_TEST_GROUP( getDay );
}
int main( int argc, char const **argv ){
return UnityMain( argc, argv, RunAllTests );}
CMakeList.txtを変更する<CMakeList.txt>cmake_minimum_required( VERSION 2.8 )project( unity_test )
set( UNITY_ROOT Unity-master )
include_directories( ${UNITY_ROOT}/src )include_directories( ${UNITY_ROOT}/lib )include_directories( ${UNITY_ROOT}/extras/fixture/src )include_directories( src/ )include_directories( mocks/ )include_directories( CMock-master/src )link_libraries( m )
set(UNITY_FILES
${UNITY_ROOT}/src/unity.c
${UNITY_ROOT}/extras/fixture/src/unity_fixture.c)
set(CMOCK_FILES
CMock-master/src/cmock.c
)
続きset(
TEST_FILES
test/testcase/test_succ.ctest/testcase/test_getDay.c
)
set(SOURCE_FILES
src/succ.csrc/getDay.cmocks/MockgetMonth.c
)
set(TEST_RUNNER_FILES
test/testrunner/getDay_test_runner.ctest/testrunner/succ_test_runner.ctest/testrunner/all_tests.c
)
set(INCLUDE_FILES
src/getMonth.h)
execute_process( COMMAND ruby2.0 ./CMock-master/lib/cmock.rb${INCLUDE_FILES})
add_executable( unity_test ${SOURCE_FILES} ${TEST_FILES} ${TEST_RUNNER_FILES} ${UNITY_FILES} ${CMOCK_FILES} )
モックを作る対象のヘッダファイルのリスト
モックもソースファイルのリストに含めておく