著者:後藤大地
前回はコマンド開発においてテストフレームワークを使うことの重要性を説明するとともに、そうした目的で利用できるフレームワークとして「Kyua」(https://github.com/jmmv/kyua)を紹介した。Kyuaは、特にコマンドの動作チェックに適したフレームワークなので、この連載で取り上げるには最適なものの一つといえる。
こうしたテストフレームワークを活用してコマンドを開発していくには、最初に開発用の場所をちゃんと用意しておく方がよい。あとからフレームワークを導入しようとしても中途半端なものになったり、面倒くさくなって使わないままになったりするからだ。コーディングの初期段階からKyauを利用できる状況にしておくことで、シームレスにテストフレームワークを利用しながら開発を進めることができる。
今回はそうしたテストフレームワークを活用した開発を実現できるように、どのようにディレクトリやファイルを配置し、初期段階からテストを取り込んだ開発をどのように実施していくのかを説明する。
記事本文掲載のシェルスクリプトマガジンvol.50は以下リンク先でご購入できます。
1 2 3 4 |
% echo 1 2 3 | ./bin/sum 1 2 3 6 % |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
% tree -F ore ore/ ├── bin/ ├── Makefile ├── Makefile.inc ├── mk/ │ └── ore.mk ├── src/ │ ├── bin/ │ │ ├─ Makefile │ │ ├─ Makefile.inc │ │ └─ sum/ │ │ ├── Makefile │ │ ├── Makefile.inc │ │ └── sum.c │ ├── Makefile │ └── Makefile.inc └── tests/ ├── bin/ │ ├── Kyuafile │ ├── Makefile │ ├── Makefile.inc │ └── sum/ │ ├── Kyuafile │ ├── Makefile │ ├── Makefile.inc │ └── test.atf-sh* ├── Kyuafile ├── Makefile └── Makefile.inc 8 directories, 20 files % |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
syntax(2) local directory = fs.dirname(current_kyuafile()) for file in fs.files(directory) do if file == "." or file == ".." then -- Skip these special entries. else local kyuafile_relative = fs.join(file,"Kyuafile") localkyuafile_absolute=fs.join(directory, kyuafile_relative) if fs.exists(kyuafile_absolute) then include(kyuafile_relative) end end end |
1 2 3 4 5 |
syntax(2) test_suite("ore") atf_test_program{name="test.atf-sh", } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
% cd tests % kyua test bin/sum/test.atf-sh:normal -> passed [0.008s] Results file id is Users_daichi_Documents_shellmagazine_201710_stuffs_ore1_tests.20170813-023317-838743 Results saved to /Users/daichi/.kyua/store/results.Users_daichi_Documents_shellmagazine_201710_stuffs_ore1_tests.20170813-023317-838743.db 1/1 passed (0 failed) % cd bin % kyua test sum/test.atf-sh:normal -> passed [0.011s] Results file id is Users_daichi_Documents_shellmagazine_201710_stuffs_ore1_tests_bin.20170813-023320-333630 Results saved to /Users/daichi/.kyua/store/results.Users_daichi_Documents_shellmagazine_201710_stuffs_ore1_tests_bin.20170813-023320-333630.db 1/1 passed (0 failed) % cd sum % kyua test test.atf-sh:normal -> passed [0.009s] Results file id is Users_daichi_Documents_shellmagazine_201710_stuffs_ore1_tests_bin_sum.20170813-023323-220477 Results saved to /Users/daichi/.kyua/store/results.Users_daichi_Documents_shellmagazine_201710_stuffs_ore1_tests_bin_sum.20170813-023323-220477.db 1/1 passed (0 failed) % |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
ore/ ├── Makefile ├── Makefile.inc ├── mk/ │ └── ore.mk ├── src/ │ ├── bin/ │ │ ├── Makefile │ │ ├── Makefile.inc │ │ └── sum/ │ │ ├── Makefile │ │ └── Makefile.inc │ ├── Makefile │ └── Makefile.inc └── tests/ ├── bin/ │ ├── Makefile │ ├── Makefile.inc │ └── sum/ │ ├── Makefile │ └── Makefile.inc ├── Makefile └── Makefile.inc |
1 |
.include "Makefile.inc" |
1 |
.include "../Makefile.inc" |
1 |
.include "mk/ore.mk" |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
ore/ ├── Makefile ├── Makefile.inc ├── mk/ │ └── ore.mk ├── src/ │ ├── bin/ │ │ ├── Makefile │ │ ├── Makefile.inc │ │ └── sum/ │ │ ├── Makefile │ │ └── Makefile.inc │ ├── Makefile │ └── Makefile.inc └── tests/ ├── bin/ │ ├── Makefile │ ├── Makefile.inc │ └── sum/ │ ├── Makefile │ └── Makefile.inc ├── Makefile └── Makefile.inc |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
# コマンド設定 CC?= cc CHMOD?= chmod ENV?= env FIND?= find GREP?= grep KYUA?= kyua LS?= ls MAKE?= make RM?= rm SED?= sed # ディレクトリ設定 .if !defined(TOPDIR) . if exists(${.CURDIR}/mk/ore.mk) TOPDIR= ${.CURDIR} . elif exists(${.CURDIR}/../mk/ore.mk) TOPDIR= ${.CURDIR:C,/[^/]*$,,} . elif exists(${.CURDIR}/../../mk/ore.mk) TOPDIR= ${.CURDIR:C,/[^/]*/[^/]*$,,} . elif exists(${.CURDIR}/../../../mk/ore.mk) TOPDIR= ${.CURDIR:C,/[^/]*/[^/]*/[^/]*$,,} . endif .endif WORKPLACE= ${.CURDIR:S,${TOPDIR},,:C,^$,/,} DIRNAME= ${WORKPLACE:C,${WORKPLACE:C,[^/]*$,,},,} BINDIR?= ${TOPDIR}/bin SRCDIR?= ${TOPDIR}/src TESTDIR?= ${TOPDIR}/tests # パーミッション設定 BINPERM?= 555 # コンパイルオプション CFLAGS?= -I. -O2 -pipe # 対応ターゲット # TARGET build clean test # ore/ OK OK OK # bin/ - - - # mk/ - - - # src/ OK OK OK # bin/ OK OK OK # */ OK OK OK # tests/ OK OK OK # bin/ OK OK OK # */ OK OK OK # ビルド・テスト処理 # / .if ${WORKPLACE} == "/" build: cd ${SRCDIR}; ${MAKE} $@ clean: cd ${SRCDIR}; ${MAKE} $@ test: cd ${TESTDIR}; ${MAKE} $@ # /src .elif ${WORKPLACE} == "/src" build: cd ${SRCDIR}/bin; ${MAKE} $@ clean: cd ${SRCDIR}/bin; ${MAKE} $@ test: cd ${TESTDIR}; ${MAKE} $@ # /src/bin .elif ${WORKPLACE} == "/src/bin" TARGETDIRS!= ${FIND} . -type d -maxdepth 1 | ${SED} 's,^[.][/]*,,' build: ${MAKE} build-recursive clean: ${MAKE} clean-recursive test: cd ${TESTDIR}/bin; ${MAKE} $@ # /src/bin/* .elif ${WORKPLACE:C,/[^/]*$,,} == "/src/bin" OBJS!= ${LS} | ${GREP} '[.]c$$' 2> /dev/null | ${SED} 's/.c$$/.o/g' CLEANFILES?= ${DIRNAME} ${BINDIR}/${DIRNAME} ${OBJS} ${DIRNAME}.core build: ${DIRNAME} ${DIRNAME}: ${OBJS} ${RM} -f ${BINDIR}/${DIRNAME} ${CC} ${CFLAGS} -o ${BINDIR}/${DIRNAME} ${OBJS} ${CHMOD} ${BINPERM} ${BINDIR}/${DIRNAME} .c.o: ${CC} ${CFLAGS} -c $< -o $@ clean: ${RM} -f ${CLEANFILES} test: build cd ${TESTDIR}/${WORKPLACE:C,^/src/,,}; ${MAKE} $@ # /tests .elif ${WORKPLACE} == "/tests" TARGETDIRS!= ${FIND} . -type d -maxdepth 1 | ${SED} 's,^[.][/]*,,' test: ${ENV} PATH=${BINDIR}:${PATH} ${KYUA} $@ build: cd ${SRCDIR}; ${MAKE} $@ clean: cd ${SRCDIR}; ${MAKE} $@ # /tests/bin .elif ${WORKPLACE} == "/tests/bin" TARGETDIRS!= ${FIND} . -type d -maxdepth 1 | ${SED} 's,^[.][/]*,,' test: ${ENV} PATH=${BINDIR}:${PATH} ${KYUA} $@ build: cd ${SRCDIR}/bin; ${MAKE} $@ clean: cd ${SRCDIR}/bin; ${MAKE} $@ # /tests/bin/* .elif ${WORKPLACE:C,/[^/]*$,,} == "/tests/bin" test: ${ENV} PATH=${BINDIR}:${PATH} ${KYUA} $@ build: cd ${SRCDIR}/${WORKPLACE:C,^/tests/,,}; ${MAKE} $@ clean: cd ${SRCDIR}/${WORKPLACE:C,^/tests/,,}; ${MAKE} $@ .endif build-recursive: .for dir in ${TARGETDIRS} cd ${dir}; ${MAKE} build .endfor clean-recursive: .for dir in ${TARGETDIRS} cd ${dir}; ${MAKE} clean .endfor |
1 2 3 4 5 6 7 8 9 10 11 |
# コマンド設定 CC?= cc CHMOD?= chmod ENV?= env FIND?= find GREP?= grep KYUA?= kyua LS?= ls MAKE?= make RM?= rm SED?= sed |
1 2 3 4 5 6 7 8 9 10 11 12 |
# ディレクトリ設定 .if !defined(TOPDIR) . if exists(${.CURDIR}/mk/ore.mk) TOPDIR= ${.CURDIR} . elif exists(${.CURDIR}/../mk/ore.mk) TOPDIR= ${.CURDIR:C,/[^/]*$,,} . elif exists(${.CURDIR}/../../mk/ore.mk) TOPDIR= ${.CURDIR:C,/[^/]*/[^/]*$,,} . elif exists(${.CURDIR}/../../../mk/ore.mk) TOPDIR= ${.CURDIR:C,/[^/]*/[^/]*/[^/]*$,,} . endif .endif |
1 2 3 4 5 6 |
WORKPLACE= ${.CURDIR:S,${TOPDIR},,:C,^$,/,} DIRNAME= ${WORKPLACE:C,${WORKPLACE:C,[^/]*$,,},,} BINDIR?= ${TOPDIR}/bin SRCDIR?= ${TOPDIR}/src TESTDIR?= ${TOPDIR}/test |
1 2 |
# コンパイルオプション CFLAGS?= -I. -O2 -pipe |
1 2 3 4 5 6 7 8 |
# /src .elif ${WORKPLACE} == "/src" build: cd ${SRCDIR}/bin; ${MAKE} $@ clean: cd ${SRCDIR}/bin; ${MAKE} $@ test: cd ${TESTDIR}; ${MAKE} $@ |
1 2 3 4 5 6 7 8 9 |
# /src/bin .elif ${WORKPLACE} == "/src/bin" TARGETDIRS!= ${FIND} . -type d -maxdepth 1 | ${SED} 's,^[.][/]*,,' build: ${MAKE} build-recursive clean: ${MAKE} clean-recursive test: cd ${TESTDIR}/bin; ${MAKE} $@ |
1 2 3 4 5 6 7 8 9 |
build-recursive: .for dir in ${TARGETDIRS} cd ${dir}; ${MAKE} build .endfor clean-recursive: .for dir in ${TARGETDIRS} cd ${dir}; ${MAKE} clean .endfor |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# /src/bin/* .elif ${WORKPLACE:C,/[^/]*$,,} == "/src/bin" OBJS!= ${LS} | ${GREP} '[.]c$$' 2> /dev/null | ${SED} 's/.c$$/.o/g' CLEANFILES?= ${DIRNAME} ${BINDIR}/${DIRNAME} ${OBJS} ${DIRNAME}.core build: ${DIRNAME} ${DIRNAME}: ${OBJS} ${RM} -f ${BINDIR}/${DIRNAME} ${CC} ${CFLAGS} -o ${BINDIR}/${DIRNAME} ${OBJS} ${CHMOD} ${BINPERM} ${BINDIR}/${DIRNAME} .c.o: ${CC} ${CFLAGS} -c $< -o $@ clean: ${RM} -f ${CLEANFILES} test: build cd ${TESTDIR}/${WORKPLACE:C,^/src/,,}; ${MAKE} $@ |
1 2 3 4 5 6 7 8 9 |
# /tests .elif ${WORKPLACE} == "/tests" TARGETDIRS!= ${FIND} . -type d -maxdepth 1 | ${SED} 's,^[.][/]*,,' test: ${ENV} PATH=${BINDIR}:${PATH} ${KYUA} $@ build: cd ${SRCDIR}; ${MAKE} $@ clean: cd ${SRCDIR}; ${MAKE} $@ |
1 2 3 4 5 6 7 8 9 |
# /tests/bin .elif ${WORKPLACE} == "/tests/bin" TARGETDIRS!= ${FIND} . -type d -maxdepth 1 | ${SED} 's,^[.][/]*,,' test: ${ENV} PATH=${BINDIR}:${PATH} ${KYUA} $@ build: cd ${SRCDIR}/bin; ${MAKE} $@ clean: cd ${SRCDIR}/bin; ${MAKE} $@ |
1 2 3 4 5 6 7 8 9 |
# /tests/bin/* .elif ${WORKPLACE:C,/[^/]*$,,} == "/tests/bin" test: ${ENV} PATH=${BINDIR}:${PATH} ${KYUA} $@ build: cd ${SRCDIR}/${WORKPLACE:C,^/tests/,,}; ${MAKE} $@ clean: cd ${SRCDIR}/${WORKPLACE:C,^/tests/,,}; ${MAKE} $@ .endif |