-
Notifications
You must be signed in to change notification settings - Fork 53
/
Copy pathTUTORIAL.ja
1941 lines (1450 loc) · 56.6 KB
/
TUTORIAL.ja
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# -*- rd -*-
= チュートリアル --- Cutterの使い方
Copyright (C) 2007-2011 Kouhei Sutou <[email protected]>
: ライセンス
((<LGPLv3+|URL:URL:http://www.gnu.org/licenses/lgpl.html>))と
((<GFDLv1.3+|URL:http://www.gnu.org/licenses/fdl.html>))と
((<CC-BY-SA|URL:http://creativecommons.org/licenses/by-sa/3.0/>))
のトリプルライセンス。
== はじめに
スタックを実現するプログラム(ライブラリ)をC言語で作成する。
プログラム作成はテストを作成しながら行う。テストの作成にはC
言語用のテスティングフレームワークであるCutterを用いる。
プログラムのビルドシステムにはGNUビルドシステム(GNU
Autoconf/GNU Automake/GNU Libtool)を使用する。GNUビルドシス
テムはビルド環境の差異を吸収する。これによりプログラム・テス
トを複数の環境で容易にビルドできるようになる。
大きなコストをかけずにプログラム本体が複数の環境で動作するの
であれば、その方がよい。さらにテストもその環境で動作するのな
らば、プログラム本体がその環境で正しく動作することを容易に検
証できる。プログラム本体だけではなく、テストも複数の環境で容
易に動作することは重要である。
Cutterが依存しているライブラリはGLibのみである。GLibはUNIX系
のシステムだけではなく、WindowsやMac OS X上でも動作する移植
性の高いライブラリである。CutterはGLibを利用することにより移
植性の高い状態を保ちつつ豊富なテスト支援機能を提供するxUnit
系のテスティングフレームワークである。
以下、スタックを作成しながらCutterの使い方について述べる。
なお、Cutterはインストールされているものとする。
このプログラムのソースコード一式はsample/stack/以下にある。
== ディレクトリ構成
まず、プログラムを作成するためのディレクトリを用意する。ディ
レクトリはstackとする。
% mkdir -p /tmp/stack
% cd /tmp/stack
続いて、stack/ディレクトリ以下にビルド補助ファイル用ディレク
トリconfig/、プログラム用ディレクトリsrc/、テストプログラム用
ディレクトリtest/を作成する。
[stack]% mkdir config src test
つまり、ディレクトリ構成は以下のようになる。
stack/ -+- config/ ビルド補助用ディレクトリ
|
+- src/ ソースファイル用ディレクトリ
|
+- test/ テストプログラム用ディレクトリ
== GNUビルドシステム化
GNUビルドシステムでは、コマンドを実行し、いくつかのファイルを
自動生成する。これらのコマンドは、autogen.shというシェルスク
リプトを作成し、そこから呼び出すのが一般的である。ここでも、
その慣習に従う。
autogen.sh:
#!/bin/sh
run()
{
$@
if test $? -ne 0; then
echo "Failed $@"
exit 1
fi
}
run aclocal ${ACLOCAL_ARGS}
run libtoolize --copy --force
run autoheader
run automake --add-missing --foreign --copy
run autoconf
autogen.shに実行権を付けることを忘れないこと。
[stack]% chmod +x autogen.sh
run()はコマンドの実行結果を確認するための便利のための関数で
ある。実行しているコマンドはそれぞれ以下のためである。
* aclocal: Automakeが利用するマクロをaclocal.m4に集める
* libtoolize: libtoolを使用するために必要なファイルを用意
* autoheader: configureスクリプトが利用するconfig.h.inファイルを作成
* automake: configureスクリプトが利用するMakefile.inを生成
* autoconf: configureスクリプトを生成
もし、Cutterをaclocalと異なるprefixでインストールしている場合
はACLOCAL_ARGS環境変数を指定する。この環境変数はautogen.sh内
で参照している。ここでは、prefixを$HOME/localとしてCutterをイ
ンストールしたものとする。
[stack]% export ACLOCAL_ARGS="-I $HOME/local/share/aclocal"
この時点でautogen.shを実行すると以下のようになる。
[stack]% ./autogen.sh
aclocal: `configure.ac' or `configure.in' is required
Failed aclocal
Autoconf用のファイルであるconfigure.acを用意する必要がある。
=== configure.ac
autogen.shのための最低限のconfigure.acは以下の通りである。
configure.ac:
AC_PREREQ(2.59)
AC_INIT(stack, 0.0.1, [email protected])
AC_CONFIG_AUX_DIR([config])
AC_CONFIG_HEADER([src/config.h])
AM_INIT_AUTOMAKE($PACKAGE_NAME, $PACKAGE_VERSION)
AC_PROG_LIBTOOL
AC_CONFIG_FILES([Makefile])
AC_OUTPUT
configure.acを用意してもう一度autogen.shを実行すると以下のよ
うになる。
[stack]% ./autogen.sh
Putting files in AC_CONFIG_AUX_DIR, `config'.
configure.ac:7: installing `config/install-sh'
configure.ac:7: installing `config/missing'
automake: no `Makefile.am' found for any configure output
Failed automake --add-missing --foreign --copy
今度はAutomakeのためにMakefile.amを用意する必要がある。
=== Makefile.am
autogen.shのためだけであれば空のMakefile.amで構わない。
[stack]% touch Makefile.am
[stack]% ./autogen.sh
Putting files in AC_CONFIG_AUX_DIR, `config'.
これでconfigureスクリプトが生成される。この時点で一般的なソ
フトウェアのようにconfigure; make; make installができるよう
になる。
[stack]% ./configure
...
[stack]% make
[stack]% make install
ただし、ビルドするものもインストールするものも何もないため、
今は何も起きない。
== はじめてのテスト作成
最低限のビルド環境が整ったので、テストの作成にはいる。まずは、
新しく作ったばかりのスタックは空であることをテストする。コー
ドにすると以下の通りである。
void
test_new_stack (void)
{
Stack *stack;
stack = stack_new();
if (stack_is_empty(stack))
PASS;
else
FAIL;
}
ここでは、上記のテストをCutterのテストとして動作させる。
=== テストプログラムの作成
テストプログラムはtest/以下に作成する。ここでは
test/test-stack.cとして作成するものとする。
まず、Cutterを使うためにcutter.hをincludeする。
test/test-stack.c:
#include <cutter.h>
また、テスト対象のスタックの実装のAPIが書かれているstack.hも
includeする。(stack.hは後で作成する。)
test/test-stack.c:
#include <stack.h>
続いて、このスタックのAPIを用いてテストを作成する。
test/test-stack.c:
void
test_new_stack (void)
{
Stack *stack;
stack = stack_new();
cut_assert(stack_is_empty(stack));
}
cut_assert()は引数が0ならテストが失敗、0以外ならテストが成功
と判断するマクロである。Cutterのテストとはcut_XXX()マクロを使
用して、特定の状況で望んだ動作をしているかを検証するプログラ
ムを作成するということである。
以下に、「作成したばかりのスタックは空である」ということを検
証するテストのソースコード全体を示す。
test/test-stack.c:
#include <cutter.h>
#include <stack.h>
void test_new_stack (void);
void
test_new_stack (void)
{
Stack *stack;
stack = stack_new();
cut_assert(stack_is_empty(stack));
}
=== テストのビルド
Cutterの各テストは共有ライブラリになる。上記で作成したテスト
を共有ライブラリとしてビルドするために、Makefile.amを変更す
る。
==== test/以下でのビルド設定
現在のMakefile.amは空である。
まず、make経由でaclocalが実行された場合にもautogen.sh用に設
定したACLOCAL_ARGS環境変数が使われるように以下を追記する。
Makefile.am:
ACLOCAL_AMFLAGS = $$ACLOCAL_ARGS
次に、サブディレクトリであるtest/以下のtest/test-stack.cをビ
ルドするためにはMakefile.amにtest/以下がサブディレクトリとし
て存在することを指定する。
Makefile.am:
...
SUBDIRS = test
Makefile.amを変更した後にmakeを実行すると、makeがMakefile.am
の変更を検出し、Makefileなどを自動的に更新する。
[stack]% make
cd . && /bin/sh /tmp/stack/config/missing --run automake-1.10 --foreign Makefile
cd . && /bin/sh ./config.status Makefile
config.status: creating Makefile
Making all in test
make[1]: ディレクトリ `/tmp/stack/test' に入ります
make[1]: *** ターゲット `all' を make するルールがありません. 中止.
make[1]: ディレクトリ `/tmp/stack/test' から出ます
make: *** [all-recursive] エラー 1
test/以下もビルドしにいこうとしているのがわかる。ただし、
test/Makefileがないためtest/以下でのビルドは失敗している。
test/以下でビルドを行うようにするため、test/Makefile.amを作成
する。また、configureがtest/Makefileを生成するように
configure.acに指定する。
test/以下でのmakeが失敗しないようにするには、空の
test/Makefile.amでもよい。
[stack]% touch test/Makefile.am
あとはconfigure.acにtest/Makefileを生成するように指定すれば
makeは通るようになる。
configure.ac:
...
AC_CONFIG_FILES([Makefile
test/Makefile])
...
実際にmakeを実行すると自動で再びconfigureが走り、
test/Makefileが生成され、test/以下でのmakeが失敗しなくなる。
[stack]% make
...
config.status: creating test/Makefile
config.status: creating src/config.h
config.status: src/config.h is unchanged
config.status: executing depfiles commands
Making all in test
make[1]: ディレクトリ `/tmp/stack/test' に入ります
make[1]: `all' に対して行うべき事はありません.
make[1]: ディレクトリ `/tmp/stack/test' から出ます
make[1]: ディレクトリ `/tmp/stack' に入ります
make[1]: `all-am' に対して行うべき事はありません.
make[1]: ディレクトリ `/tmp/stack' から出ます
==== test/test_stack.soのビルド
それではtest/test-stack.cを共有ライブラリとしてビルドできる
ようにtest/Makefile.amを編集する。テスト用の共有ライブラリは
「test_」から始まる名前にする(「test_」の前に「lib」が付い
ても良い)。また、テストプログラムはインストールする必要がな
いため「noinst_」を使う。
test/Makefile.am:
noinst_LTLIBRARIES = test_stack.la
テストの共有ライブラリはCutterが提供するテスト実行コマンド
cutterから動的に読み込まれる。動的に読み込まれる共有ライブラ
リは、libtoolに-moduleオプションを渡す必要がある。ま
た、-moduleオプションを指定する場合-rpathも指定する必要がある。
そこで、LDFLAGSを以下のように指定する。-avoid-versionはテスト
の共有ライブラリにはバージョン番号を付ける必要がないため指定
している。-no-undefinedは未定義のシンボルがある場合にエラーを
報告するオプションである。環境によっては-no-undefinedを指定し
ないと共有ライブラリが作成されないため指定している。(例えば、
Windows上でDLLを作成する場合)
test/Makefile.am:
...
LDFLAGS = -module -rpath $(libdir) -avoid-version -no-undefined
test/test_stack.laのビルド(test_stack.soはtest/.libs/以下に
作成される)にはtest/test-stack.cを使用するので、それを指定す
る。
test/Makefile.am:
...
test_stack_la_SOURCES = test-stack.c
これでtest/test_stack.laがビルドできる。
[stack]% make
...
cd .. && /bin/sh /tmp/stack/config/missing --run automake-1.10 --foreign test/Makefile
test/Makefile.am: required file `config/depcomp' not found
test/Makefile.am: `automake --add-missing' can install `depcomp'
make[1]: *** [Makefile.in] エラー 1
...
config/depcompを生成するには--add-missingオプション付きで
automakeを実行する必要がある。これにはautogen.shを使用できる。
また、configureを再実行する必要もある。
[stack]% ./autogen.sh
[stack]% ./configure
これでmakeを実行することによりtest/test_stack.laができるよう
になる。
[stack]% make
...
test-stack.c:1:20: error: cutter.h: そのようなファイルやディレクトリはありません
test-stack.c:2:19: error: stack.h: そのようなファイルやディレクトリはありません
test-stack.c: In function ‘test_new_stack’:
test-stack.c:9: error: ‘Stack’ undeclared (first use in this function)
test-stack.c:9: error: (Each undeclared identifier is reported only once
test-stack.c:9: error: for each function it appears in.)
test-stack.c:9: error: ‘stack’ undeclared (first use in this function)
make[1]: *** [test-stack.lo] エラー 1
make[1]: ディレクトリ `/tmp/stack/test' から出ます
make: *** [all-recursive] エラー 1
ただし、上記のようにCutterを使用する設定を行っていないため
cutter.hが読み込めない。また、スタックの実装もないため
stack.hの読み込みにも失敗する。
==== Cutterの使用
まずは、cutter.hを読み込めるようにする。Cutterはaclocal用のマ
クロファイルを提供している。そのため、容易にGNUビルドシステム
から利用することができる。
まず、configure.acにCutterを検出するコードを追加する。
configure.ac:
...
AC_CHECK_CUTTER
AC_CONFIG_FILES([Makefile
test/Makefile])
...
また、test/Makefile.amでは検出したCutter用の設定を利用する。
test/Makefile.am:
...
INCLUDES = $(CUTTER_CFLAGS)
LIBS = $(CUTTER_LIBS)
...
現時点での完全なconfigure.ac、Makefile.am、test/Makefile.amは
以下のようになる。
configure.ac:
AC_PREREQ(2.59)
AC_INIT(stack, 0.0.1, [email protected])
AC_CONFIG_AUX_DIR([config])
AC_CONFIG_HEADER([src/config.h])
AM_INIT_AUTOMAKE($PACKAGE_NAME, $PACKAGE_VERSION)
AC_PROG_LIBTOOL
AC_CHECK_CUTTER
AC_CONFIG_FILES([Makefile
test/Makefile])
AC_OUTPUT
Makefile.am:
ACLOCAL_AMFLAGS = $$ACLOCAL_ARGS
SUBDIRS = test
test/Makefile.am:
noinst_LTLIBRARIES = test_stack.la
INCLUDES = $(CUTTER_CFLAGS)
LIBS = $(CUTTER_LIBS)
LDFLAGS = -module -rpath $(libdir) -avoid-version -no-undefined
test_stack_la_SOURCES = test-stack.c
AC_CHECK_CUTTERマクロ内では一般的なパッケージ情報管理ツールで
あるpkg-configを使用している。もし、Cutterをpkg-configと異な
るprefixでインストールしている場合はPKG_CONFIG_PATH環境変数を
指定する。この環境変数はpkg-configが.pcファイルを検索するため
に利用する。ここではprefixを$HOME/localとしてCutterをインストー
ルしたものとする。
[stack]% export PKG_CONFIG_PATH=$HOME/local/lib/pkgconfig
変更後、makeを実行すると自動的にconfigureが実行され、Cutter
を利用したビルドが行われる。
[stack]% make
...
test-stack.c:2:19: error: stack.h: そのようなファイルやディレクトリはありません
test-stack.c: In function ‘test_new_stack’:
test-stack.c:9: error: ‘Stack’ undeclared (first use in this function)
test-stack.c:9: error: (Each undeclared identifier is reported only once
test-stack.c:9: error: for each function it appears in.)
test-stack.c:9: error: ‘stack’ undeclared (first use in this function)
make[1]: *** [test-stack.lo] エラー 1
make[1]: ディレクトリ `/tmp/stack/test' から出ます
make: *** [all-recursive] エラー 1
cutter.hが読み込めないというエラーがなくなった。
==== スタックAPIの作成
次はstack.hが読み込めないエラーを解消する。
スタックの実装はsrc/以下に作成するので、スタックのAPIである
stack.hはsrc/stack.hに置く。
[stack]% touch src/stack.h
インクルードパスを設定し、テストプログラムからstack.hを読み
込めるようにする。
test/Makefile.am:
...
INCLUDES = $(CUTTER_CFLAGS) -I$(top_srcdir)/src
...
makeを実行するとstack.hが読み込めないエラーが解消されている
のが分かる。
[stack]% make
...
test-stack.c: In function ‘test_new_stack’:
test-stack.c:9: error: ‘Stack’ undeclared (first use in this function)
test-stack.c:9: error: (Each undeclared identifier is reported only once
test-stack.c:9: error: for each function it appears in.)
test-stack.c:9: error: ‘stack’ undeclared (first use in this function)
make[1]: *** [test-stack.lo] エラー 1
make[1]: ディレクトリ `/tmp/stack/test' から出ます
make: *** [all-recursive] エラー 1
残りのエラーがStack型が宣言されていないことだけになった。
==== Stack型の宣言
src/stack.hにStack型を宣言し、テストプログラムをビルドできる
ようにする。
src/stack.h:
#ifndef __STACK_H__
#define __STACK_H__
typedef struct _Stack Stack;
#endif
stack_new()が宣言されていないため警告がでるが共有ライブラリ
を作成することはできる。
[stack]% make
...
test-stack.c: In function ‘test_new_stack’:
test-stack.c:10: warning: assignment makes pointer from integer without a cast
...
[stack]% file test/.libs/test_stack.so
test/.libs/test_stack.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, not stripped
注: Cygwin上では未解決のシンボルがあるときは作成できない。
Cygwin環境の場合は気にせずに次に進むこと。
==== stack_new()/stack_is_empty()の宣言
stack_new()、stack_is_empty()を宣言し、警告を解消する。
src/stack.h:
...
Stack *stack_new (void);
int stack_is_empty (Stack *stack);
...
makeをして警告がでないことを確認する。
[stack]% make
=== テスト起動
共有ライブラリが作成できたので、cutterコマンドでこのテストを
起動できる。
[stack]% cutter test/
cutter: symbol lookup error: test/.libs/test_stack.so: undefined symbol: stack_new
stack_new()が定義されていないため読み込みに失敗するが、テス
トプログラムが読み込まれることは確認できる。
注: Cygwin上では未解決のシンボルがあるときはDLL作成できないの
で、エラーにならず、「0個のテストを実行して失敗しなかった」結
果が報告される。以降の作業の中でスタックを実装し、未解決のシ
ンボルが解決されればDLLが作成され、テストが実行できるようにな
る。それまではテストの実行結果が異なる。Cygwin環境の場合は気
にせずに次に進むこと。
==== テスト起動の自動化
GNUビルドシステムでは一般的にmake checkでテストが起動する。
スタックの実装でも同様にテストが起動するようにする。
まず、テストを起動するスクリプトtest/run-test.shを作成する。
cutterコマンドのパスは環境変数CUTTERで受け取ることにする。
test/run-test.sh:
#!/bin/sh
export BASE_DIR="`dirname $0`"
$CUTTER -s $BASE_DIR "$@" $BASE_DIR
実行権を付けることを忘れないこと。
[stack]% chmod +x test/run-test.sh
test/Makefile.amにテスト起動スクリプトとしてtest/run-test.sh
を使うことを指定する。
test/Makefile.am:
TESTS = run-test.sh
TESTS_ENVIRONMENT = CUTTER="$(CUTTER)"
...
TESTS_ENVIRONMENTではcutterコマンドのパスを環境変数CUTTERで
渡している。cutterコマンドのパスはconfigure.ac内に追加した
AC_CHECK_CUTTERが検出している。
make -s checkでテストが走ることを確認する。-sオプションは
makeの出力を抑えるオプション(silent)であり、これを指定する
ことによりテスト結果が見やすくなる。
[stack]% make -s check
Making check in test
cutter: symbol lookup error: ./.libs/test_stack.so: undefined symbol: stack_new
FAIL: run-test.sh
================================
1 of 1 tests failed
Please report to [email protected]
================================
...
注: 前述のとおり、Cygwin上ではDLLが作成できないため、エラーは
発生しない。Cygwin環境の場合は実行結果が異なっても気にせずに
次に進むこと。
==== test/run-test.shの単独実行のサポート
make -s checkではビルドログなどテスト結果以外の出力がでて、テス
ト結果が埋もれてしまう。そこで、make -s check経由ではなく
test/run-test.shを実行できるようにする。
まず、test/run-test.shを環境変数CUTTERが指定されていない場合
は、cutterコマンドのパスを自動的に検出する。さらにmake check
経由でtest/run-test.shが起動された場合はmakeを実行し、必要な
ファイルをビルドする。
test/run-test.sh:
#!/bin/sh
export BASE_DIR="`dirname $0`"
top_dir="$BASE_DIR/.."
if test -z "$NO_MAKE"; then
make -C $top_dir > /dev/null || exit 1
fi
if test -z "$CUTTER"; then
CUTTER="`make -s -C $BASE_DIR echo-cutter`"
fi
$CUTTER -s $BASE_DIR "$@" $BASE_DIR
このtest/run-test.shに対応するためにtest/Makefile.amを以下の
ように変更する。
test/Makefile.am:
...
TESTS_ENVIRONMENT = NO_MAKE=yes CUTTER="$(CUTTER)"
...
echo-cutter:
@echo $(CUTTER)
test/Makefile.am全体は以下のようになる。
test/Makefile.am:
TESTS = run-test.sh
TESTS_ENVIRONMENT = NO_MAKE=yes CUTTER="$(CUTTER)"
noinst_LTLIBRARIES = test_stack.la
INCLUDES = $(CUTTER_CFLAGS) -I$(top_srcdir)/src
LIBS = $(CUTTER_LIBS)
LDFLAGS = -module -rpath $(libdir) -avoid-version -no-undefined
test_stack_la_SOURCES = test-stack.c
echo-cutter:
@echo $(CUTTER)
test/run-test.shを直接実行してテストが起動することを確認する。
[stack]% test/run-test.sh
cutter: symbol lookup error: test/.libs/test_stack.so: undefined symbol: stack_new
注: Cygwin環境ではエラーは発生しない。
ここからはmake -s checkではなくtest/run-test.shを使用する。こ
れは必要な情報のみが出力され、本当に興味のあるテストの結果が
埋もれてしまうのを防ぐためである。
また、スタックの実装を行う前にテストの実行環境を整備している
のは、テストを実行するコストを下げるためである。これは、テス
トを実行することが面倒になるとテストを実行しなくなり、その結
果、プログラムの品質低下につながるためである。
最初にテスト環境の整備を行うと、その分、プログラム本体の開発
着手が遅れてしまう。しかし、プログラム本体が開発・保守され続
ける間は常にテストを実行し、品質を保持する必要があるため、最
初にテスト環境整備に当てたコストは回収可能である。今後、快適
に品質の高いプログラムの開発を行うために、最初にテスト環境の
整備を行うことは重要である。
=== スタックの実装
テスト環境の整備ができたため、スタックの実装に入る。
==== 簡単なstack_new()の実装
まず、stack_new()を定義し、実行時エラーの原因を解決する。
スタックの実装はsrc/stack.cで行う。簡単なstack_new()の実装は
以下の通りである。
src/stack.c:
#include <stdlib.h>
#include "stack.h"
Stack *
stack_new (void)
{
return NULL;
}
==== src/libstack.laのビルド
それでは、makeでsrc/stack.cをビルドできるようにする。
まず、test/以下をビルド対象に加えたように、src/以下もビルド
対象とする。
Makefile.am:
ACLOCAL_AMFLAGS = $$ACLOCAL_ARGS
SUBDIRS = src test
configure.ac:
...
AC_CONFIG_FILES([Makefile
src/Makefile
test/Makefile])
...
これでsrc/以下もビルド対象となる。
[stack]% test/run-test.sh
configure.ac:13: required file `src/Makefile.in' not found
make: *** [Makefile.in] エラー 1
src/Makefile.amを作成するとこのエラーはなくなる。
[stack]% touch src/Makefile.am
[stack]% test/run-test.sh
cutter: symbol lookup error: test/.libs/test_stack.so: undefined symbol: stack_new
注: Cygwin環境ではエラーは発生しない。
makeは通るようになるが、この時点ではsrc/stack.cはビルドされ
ないし、テストプログラムもlibstack.soをリンクしていないので
stack_new()が定義されていないエラーは変わらない。
src/Makefile.amに以下を追加し、src/stack.cからlibstack.soを
作成する。
src/Makefile.am:
lib_LTLIBRARIES = libstack.la
LDFLAGS = -no-undefined
libstack_la_SOURCES = stack.c
makeでlibstack.soが生成できるようになるはずである。
[stack]% make
...
make[1]: ディレクトリ `/tmp/stack/src' に入ります
Makefile:275: .deps/stack.Plo: そのようなファイルやディレクトリはありません
make[1]: *** ターゲット `.deps/stack.Plo' を make するルールがありません. 中止.
...
上記のエラーを修正するためにconfigureをもう一度実行する必要が
ある。
[stack]% ./configure
makeでsrc/.libs/libstack.so.0.0.0を生成することができる。
[stack]% make
[stack]% file src/.libs/libstack.so.0.0.0
src/.libs/libstack.so.0.0.0: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, not stripped
注: Cygwin上ではsrc/.libs/cyglibstack.dllが作成される。
==== src/libstack.laのリンク
libstack.soはできたがテストプログラムにはリンクしていないの
で、まだ実行時エラーは発生する。
[stack]% test/run-test.sh
cutter: symbol lookup error: test/.libs/test_stack.so: undefined symbol: stack_new
注: Cygwin環境ではエラーは発生しない。
libstack.soをリンクするためにtest/Makefile.amを以下のように
変更する。
test/Makefile.am:
...
LIBS = $(CUTTER_LIBS) $(top_builddir)/src/libstack.la
...
Cygwin・macOS・BSD環境の場合はsrc/.libs/以下に生成されるスタックを実装
したライブラリーを利用するために、src/.libs/にパスを通す必要がある。そ
のため、以下のようにtest/run-test.shでcutterを実行する前に環境変数を設
定する。
test/run-test.sh:
...
case `uname` in
CYGWIN*)
PATH="$top_dir/src/.libs:$PATH"
;;
Darwin)
DYLD_LIBRARY_PATH="$top_dir/src/.libs:$DYLD_LIBRARY_PATH"
export DYLD_LIBRARY_PATH
;;
*BSD)
LD_LIBRARY_PATH="$top_dir/src/.libs:$LD_LIBRARY_PATH"
export LD_LIBRARY_PATH
;;
*)
:
;;
esac
$CUTTER -s $BASE_DIR "$@" $BASE_DIR
テストプログラムを再リンクする必要があるため、一度make clean
してからビルドしなおす。
[stack]% make clean
[stack]% make
[stack]% test/run-test.sh
cutter: symbol lookup error: test/.libs/test_stack.so: undefined symbol: stack_is_empty
今まではstack_new()が見つからずにエラーが発生していたが、
stack_is_empty()が見つからないというエラーに変わった。これに
より、libstack.soがリンクされていることが確認できた。
注: Cygwin環境ではエラーは発生しない。
==== stack_is_empty()の実装
テストプログラム中ではstack_is_empty()の結果を以下のようにテ
ストしている。
test/test-stack.c:
...
cut_assert(stack_is_empty(stack));
...
つまり、stack_is_empty()が真を返すことをテストしている。よっ
て、src/stack.cでのstack_is_empty()は真を返す必要がある。
src/stack.c:
...
#define TRUE 1
#define FALSE 0
...
int
stack_is_empty (Stack *stack)
{
return TRUE;
}
src/stack.c全体は以下のようになる。
src/stack.c:
#include <stdlib.h>
#include "stack.h"
#define TRUE 1
#define FALSE 0
Stack *
stack_new (void)
{
return NULL;
}
int
stack_is_empty (Stack *stack)
{
return TRUE;
}
このstack_is_empty()の実装は常に真を返すため、テストは成功す
るはずである。
[stack]% test/run-test.sh
.
Finished in 0.000028 seconds
1 test(s), 1 assertion(s), 0 failure(s), 0 error(s), 0 pending(s), 0 omission(s), 0 notification(s)
100% passed
「.」がひとつ表示されているのは1つのテストがパスしたことを表
している。現在は1つしかテストがないので1つのテストにパスした
ということはすべてのテストにパスしたということである。
環境によっては表示が緑になっているはずである。これはテストが
パスしているので次に進んでもよいという意味である。
テストが動作することが確認できたので、以降ではテストを作成し
ながらスタックの実装を完成させる。
== pushの実装
まずはpushを実装する。今回の実装では、スタックにはintのみを
格納できることする。
=== pushのテスト
pushをした後はスタックのサイズが1になり、スタックは空ではなく
なるはずである。これをテストにすると以下のようになる。
test/test-stack.c:
...
void test_push (void);
...
void
test_push (void)
{
Stack *stack;
stack = stack_new();
cut_assert_equal_int(0, stack_get_size(stack));
stack_push(stack, 100);
cut_assert_equal_int(1, stack_get_size(stack));
cut_assert(!stack_is_empty(stack));
}
テストを実行すると、stack_get_size()が定義されていないため実
行時エラーになる。
[stack]% test/run-test.sh
cutter: symbol lookup error: ./test/.libs/test_stack.so: undefined symbol: stack_get_size
注: Cygwin環境ではエラーは発生しない?
このテストをパスするようにpushを実装する。
=== cut_stack_push()の実装
まずは、パスしなくても良いのでテストが動くように
stack_get_size()とstack_push()を実装する。
まず、src/stack.hに宣言を追加する。
src/stack.h:
...
int stack_get_size (Stack *stack);
void stack_push (Stack *stack, int value);
...
続いてsrc/stack.cに定義を追加する。
src/stack.c:
...
int
stack_get_size (Stack *stack)
{
return 0;
}
void
stack_push (Stack *stack, int value)
{
}
stack_get_size()が0を返しているのは、最初のstack_get_size()
は以下のように0を期待されているからである。
test/test-stack.c:
...
stack = stack_new();
cut_assert_equal_int(0, stack_get_size(stack));
...
pushの実装ができたのでテストを実行する。
[stack]% test/run-test.sh
.F
1) Failure: test_push
<1 == stack_get_size(stack)>
expected: <1>
but was: <0>
test/test-stack.c:23: test_push()
Finished in 0.000113 seconds
2 test(s), 2 assertion(s), 1 failure(s), 0 error(s), 0 pending(s), 0 omission(s), 0 notification(s)
50% passed
「F」はテストが失敗(Failure)したことを表している。環境によっ
ては表示が赤くなっているはずである。これは、テストがパスして
いないので先に進むことは危険であることを示している。popの実
装に移る前にテストをパスさせるようにpushを実装を改良するべき
だということである。
cutterからのメッセージでは、test/test-stack.cの23行目、
test_push()関数の中でstack_get_size(stack)の値が1ではなく0の
ために失敗したということを表している。該当する行は以下の通り
である。