動機
別の記事を書くのに必要だったので。
まずはとりあえず、記事を書くためにC用のユニットテストフレームワークを探していたら国産で見つけたのでトライしてみんとするなり。
作りたいものはCSVのパーサーです。
Step.1 インストール
公式ページ に書いてありますから・・・なお、私は
brew install cutter
で済ませました。
Step.2 テスト実行
今回のテスト対象は1行読み込み、「,」を\0に置き換え、それぞれ、先頭のポインタを保持する配列を作る関数を作ることです。
イメージ的にはこんな感じ
入力: "A,B,C"
作成するデータ:
データ: ['A', '\0', 'B', '\0', 'C', '\0']
ポインタ配列: array = [0, 2, 4]
こうすることでarray[2]とすることで"C"が取れるようになるわけです。
Step 2.0 構成
その前に構成を次のようにします。
-+-src
+-test
+-obj
Step.2.1 テストを書く
テスト対象はread_recordという関数です。
引数は順に
- 入力文字
- 配列
- 配列が収容できる最大のサイズ
- データ格納配列
- データ格納配列が収容できる最大のサイズ
です。
#include <cutter.h>
#include <string.h>
#include "record_read.h"
#define BUF_LEN 4096
void test_record_read(void) {
char *test = "A,B,C";
char *result[BUF_LEN];
char result_store[BUF_LEN];
csv_read_result ret = record_read(test, result, BUF_LEN, result_store, BUF_LEN);
cut_assert_not_null(result);
cut_assert_equal_int(ret, 0);
cut_assert(strcmp(result[0], "A"));
cut_assert(strcmp(result[1], "B"));
cut_assert(strcmp(result[2], "C"));
}
さて、ここで私は詰まりました。チュートリアルにあるAutotoolの使い方を知らないからです。
勉強しなければならないのですが、とりあえず、コンパイルして・・・と力技で行くことにしました(最悪)。
Step 2.1 テスト対象関数の作成
とりあえず、ヘッダを作成します。
#ifndef __CSV_READER_H__
#define __CSV_READER_H__
typedef enum csv_read_result {
csv_read_normal = 0,
csv_read_destination_is_not_enough = -1,
csv_read_destination_is_null = -2,
csv_read_array_is_null = -3,
csv_read_array_is_not_enough = -4
} csv_read_result;
/**
* @brief create string array from src by csv
*
* @param[in] src input string
* @param[out] array output array of string
* @param[in] array_len length of array
* @param[out] dst store output array
* @param[in] len length of dst
* @retval 0 normal end
* @retval -1 not enough allocated to input to dst
* @retval -2 dst is NULL
*/
csv_read_result record_read(char* src, char** array, int array_len, char* dst, int len);
#endif
えーっと、csv_read_resultはintと読み替えてください。なんか、書き方が変なんです。僕。
テスト対象コードも一緒に作っちゃいます。当然失敗する内容です。
#include <stdio.h>
#include <stdlib.h>
#include "record_read.h"
csv_read_result record_read(char* src, char** array, int array_len, char* dst, int len) {
return csv_read_normal;
}
で、コンパイルします。
#compile objects
cc -c src/record_read.c -o obj/record_read.o
cc -shared -o librecord_read.so obj/record_read.o
#compile test
cc -shared -I /usr/local/include/cutter -lcutter -I src -L . -lrecord_read -o test/libt_record_read.so test/t_record_read.c
2.2 実行
で、実行。
cutter test/
!
===============================================================================
Crash: test_record_read
===============================================================================
Finished in 0.533029 seconds (total: 0.532630 seconds)
1 test(s), 2 assertion(s), 0 failure(s), 0 error(s), 0 pending(s), 0 omission(s), 0 notification(s)
0% passed
は・・・はやい・・・
2個目のassertionで失敗しているので、いい感じかもしれません。ただ、クラッシュさせているのはよくないですよね・・・
2.3 修正
で、通るように修正です。
#include <stdio.h>
#include <stdlib.h>
#include "record_read.h"
csv_read_result record_read(char* src, char** array, int array_len, char* dst, int len) {
int index = 0; //array index
int i;
if ( dst == NULL ) return csv_read_destination_is_null;
if ( array == NULL ) return csv_read_array_is_null;
array[0] = dst;
index++;
for( i = 0; i < len; i++ ) {
if (src[i] == '\0') break;
if (src[i] == ',') {
dst[i] = '\0';
// check array length is nough
if ( index >= array_len ) {
return csv_read_array_is_not_enough;
}
array[index] = dst + i;
index++;
}
dst[i] = src[i];
}
if ( i == len ) {
dst[i] = '\0';
return csv_read_destination_is_not_enough;
}
return csv_read_normal;
}
再度コンパイルして実行。
$ sh first_compie_and_test.sh
.
Finished in 0.000625 seconds (total: 0.000122 seconds)
1 test(s), 5 assertion(s), 0 failure(s), 0 error(s), 0 pending(s), 0 omission(s), 0 notification(s)
100% passed
まずはOKですかね。