c言語テストフレームワー クunityを使ってみよう

32
C言語テストフレームワー Unityを使ってみよう 眞鍋 雄貴

Upload: others

Post on 12-Mar-2022

31 views

Category:

Documents


0 download

TRANSCRIPT

C言語テストフレームワークUnityを使ってみよう

眞鍋 雄貴

ソフトウェアテストとは

コンピュータ・コードが意図されたように動作し意図されないことはすべて実行しないように設計されていることを検証するように設計されたプロセス,あるいは,一連のプロセスである.

G. J. Meyers,ソフトウェア・テストの技法第2版より

テスト駆動開発

•実装を行うより先に,テストを作成する.実装は,そのテストをパスすることを目標に行う.

• アジャイル開発手法のエクストリーム・プログラミングの19あるプラクティスのうちの一つ.

• テスト駆動開発を行うためには,テスト実行の自動化が必要.

xUnit

• コンピュータプログラムの単体テストを行うためのテスティングフレームワークの総称

•事前に定義されたテストケースを自動的に実行する

•様々な言語に対して開発されている• https://ja.wikipedia.org/wiki/XUnit

Unity

• C言語用の軽量なxUnitテスティングフレームワーク

• https://github.com/ThrowTheSwitch/Unity

実際に触ってみよう…の前に

•下記のコマンドでソースコード一式をとってきてください.

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} )

テストケースファイルのリスト

ソースファイルのリスト

テストランナーのリスト

実行する

• ./sample_projで

• cmake .

• make

• ./unity_test

こうなる

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の単体テストができない!

対処法→モックを作る

関数A 関数B 関数C

呼び出し

呼び出し

関数Aは部分的に実行できるようになる→関数Aの単体テストができる!

関数B‘

関数Bの機能を部分的に実装したもの(モック)

モックの例

引数から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;

}

CMock

• ヘッダファイルからUnityで使用するモックのソースコードを作成するユーティリティ

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} )

モックを作る対象のヘッダファイルのリスト

モックもソースファイルのリストに含めておく

実行

• ./sample_projで

• cmake .

• make(このタイミングでモックファイルも生成される(ようにCMakeList.txtを書いてる))

• ./unity_test

こうなる

y-manabe@st110:~/sandbox/unity_practice/sample_proj$ ./unity_testUnity test run 1 of 1../staff/y-manabe/sandbox/unity_practice/sample_proj/test/testcase/test_getDay.c:20:TEST(getDay, getDay_mock_sample_test):FAIL: Expected 365 Was 355

-----------------------2 Tests 1 Failures 0 IgnoredFAIL