ファイルから一行を読み取る関数をテスト駆動開発してみた(c言語)。

テスト駆動開発を知って実際にやってみたくなった。

先日、「テスト駆動開発(TDD/Test Driven Development)」というプラクティスを知りました。そして、以下の記事を書きました。

実際にやってみたくなったので、c言語の課題で実践してみました。見様見真似でやっているので、本来のTDDとは違ったり、もっと工夫や改善できる点がありましたら教えてください。

実際にやってみた。

1.「次の目標を考える」

課題自体は、著作権やその他諸々公開できない理由があるので、To Doリストに落とした形から始めます。

– [ ]  get_next_line関数を繰り返し呼び出すと、fdで指定されたテキストファイルを1行ずつ読み取る
– [ ] ファイルを1行ずつ読み込んで、その読み取った行を終端に\n文字を含めて返す。
– [ ] \n文字で終わっていない場合、返された行には、終端の\n文字を含まないようにする
– [ ] ファイルの終わりに達した場合、返された行には、終端の\n文字を含まないようにする
– [ ] ファイルの終わりに達した場合、NULLを返す
– [ ] エラーが発生した場合、NULLを返す
– [ ] 読み取るものが何もない場合、NULLを返す
– [ ] ファイルの終わりに達した場合、返された行には、終端の\n文字を含まないようにする
– [ ] \n文字で終わっていない場合、返された行には、終端の\n文字を含まないようにする
– [ ] 標準入力でも読み取れるようにする
– [ ] BUFFER_SIZEの値が9999、10000000、1の場合に機能するようにする
– [ ] バイナリファイルを読み取るときの、未定義の動作を実装する
– [ ] read()がファイルの末尾までいっていない最後の呼び出し以降に、fdが指すファイルが変更された場合の、未定義の動作を実装する
– [ ] できるだけ少ない量を読み取るようにする(ファイル全体を読み取ってから各行を処理しない)
– [ ] -D BUFFER_SIZEフラグを使用して、コンパイルできるようにする

2.「その目標を示すテストを書く」

– [ ]  get_next_line関数を繰り返し呼び出すと、fdで指定されたテキストファイルを1行ずつ読み取る

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#define BUFFER_SIZE 42

void    test_read_file(void)
{
    int                      fd;
    char                   *answer[] = {"1234567890\n", NULL};
    char                   *line;
    int   i;

    i = 0;
    fd = open("test.txt", O_RDONLY);
    if (fd == -1)
    {
        perror("open error");
        return ;
    }

    while (answer[i] != NULL)
    {
        line = get_next_line(fd);
        assert(strcmp(line, answer[i]) == 0);
        i++;
    }
    close(fd);
    return ;
}

int main(void)
{
    test_read_file();
    return (0);
}

3.「そのテストを実行して失敗させる」

char	    *get_next_line(int fd)
{
	    (void)fd;
	    return (0);
}

=> % ./a.out
zsh: segmentation fault  ./a.out

作成したテストが失敗して、「RED」。

4.「目的のコードを書く」

char	    *get_next_line(int fd)
{
	    (void)fd;
	    return ("1234567890\n");
}

5.「2で書いたテストを成功させる」

これで成功はするが、get_next_line関数ではreadを行っていない。
readした文字列をmallocで確保した領域に格納して返したい。

6.「テストが通るままでリファクタリングを行う」

—test.txt————
1234567890
aaaaaaaaaa
————————-

#define BUFFER_SIZE 11

char	*get_next_line(int fd)
{
	char	        *line;
	ssize_t	bytes_read;

	line = (char *)malloc(sizeof(char) * (BUFFER_SIZE + 1));
	if (line == NULL)
		return (NULL);
	bytes_read = read(fd, line, BUFFER_SIZE);
	if (bytes_read == -1)
	{
		free(line);
		return (NULL);
	}
        line[bytes_read] = '\0';
	return (line);
}

void    test_read_file(void)
{
    int       fd;
    char    *answer[] = {"1234567890\n", "aaaaaaaaaa\n", NULL};
    char    *line;
    int       i;

    i = 0;
    fd = open("test.txt", O_RDONLY);
    if (fd == -1)
    {
        perror("open error");
        return ;
    }

    while (answer[i] != NULL)
    {
        line = get_next_line(fd);
        assert(strcmp(line, answer[i]) == 0);
        free(line);
        i++;
    }
    close(fd);
    return ;
}

7.「1〜6を繰り返す」

上記までで、以下の二つができた(はず)。
– [x]  get_next_line関数を繰り返し呼び出すと、fdで指定されたテキストファイルを1行ずつ読み取る
– [x] ファイルを1行ずつ読み込んで、その読み取った行を終端に\n文字を含めて返す。

1の「次の目標を考える」は、「スキルだから練習しないとできるようにならない。練習すればできるようになる。」とt_wadaさんがおっしゃっていたが、実際にやってみるとやはり難しい。一行読み取ったことをテストするということは、テスト容易性が低い(テストすることが難しい)のかもしれない。タスクに分解する過程も、重要度やテスト容易性を軸に最初のタスクを選ぶことも、どちらも難しい。

以降、進捗次第、追記していきます。

次にやることは、「1行ずつ読み取る、つまり、BUFFER_SIZEに依らず改行文字までを読みとる」こと。(上記二つのタスクは正確には終わっていなかった…。)

タイトルとURLをコピーしました