C言語でTDD

最近C言語がわからないのはあまり良くないよなということでK&R第2版をやっていたりします。評判は悪いところもあったりしますが,練習問題が豊富なのはいいですね。

こういった練習問題は求められているものが明確なのでテストを書く練習になります。ということで次のようなコードを書いたりして

yoshimi@enaga:k_and_r% cat cunit.c 
#include <stdio.h>

void assert_equal_str(char *expected, char *actual)
{
  while (*expected == *actual){
    if (*expected == '\0'){
      return;
    }
    expected++;
    actual++;
  }
  printf("expected %s, but got %s\n", expected, actual);
}

void assert_equal_int(int expected, int actual)
{
  if (expected != actual){
    printf("expected %d, but got %d\n", expected, actual);
  }
}

例えば練習問題5.8では次のようにして使ってます

yoshimi@enaga:k_and_r% cat ex5_8.c
#include <stdio.h>

void assert_equal_int(int, int);

static char daytab[2][13] = {
  {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
  {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};

int day_of_year(int year, int month, int day)
{
  int i, leap;
  leap = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
  if (month < 1 || month > 12 || day < 1 || day > daytab[leap][month]){
    return -1;
  }

  for (i = 1; i < month; i++){
    day += daytab[leap][i];
  }
  return day;
}

void test_day_of_year(void)
{
  printf("day_of_year testing:\n");
  assert_equal_int(365, day_of_year(2007, 12, 31));
  assert_equal_int(366, day_of_year(2008, 12, 31));
  printf("\tmonth error test\n");
  assert_equal_int(-1, day_of_year(2007, 0, 0));
  assert_equal_int(-1, day_of_year(2008, 0, 0));
  assert_equal_int(-1, day_of_year(2007, 13, 31));
  printf("\tmonth_day error test\n");
  assert_equal_int(-1, day_of_year(2007, 1, 0));
  assert_equal_int(-1, day_of_year(2007, 1, -1));
  assert_equal_int(-1, day_of_year(2007, 1, 32));
  assert_equal_int(-1, day_of_year(2008, 1, 32));
  assert_equal_int(-1, day_of_year(2007, 2, 29));
  assert_equal_int(-1, day_of_year(2008, 2, 30));
  assert_equal_int(-1, day_of_year(2007, 3, 32));
  assert_equal_int(-1, day_of_year(2008, 3, 32));
  assert_equal_int(-1, day_of_year(2007, 4, 31));
  assert_equal_int(-1, day_of_year(2008, 4, 31));
  assert_equal_int(-1, day_of_year(2007, 5, 32));
  assert_equal_int(-1, day_of_year(2008, 5, 32));
  assert_equal_int(-1, day_of_year(2007, 6, 32));
  assert_equal_int(-1, day_of_year(2008, 6, 32));
  assert_equal_int(-1, day_of_year(2007, 7, 32));
  assert_equal_int(-1, day_of_year(2008, 7, 32));
  assert_equal_int(-1, day_of_year(2007, 8, 32));
  assert_equal_int(-1, day_of_year(2008, 8, 32));
  assert_equal_int(-1, day_of_year(2007, 9, 32));
  assert_equal_int(-1, day_of_year(2008, 9, 32));
  assert_equal_int(-1, day_of_year(2007, 10, 32));
  assert_equal_int(-1, day_of_year(2008, 10, 32));

}

void month_day(int year, int yearday, int *pmonth, int *pday)
{
  int i, leap;

  leap = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
  if (yearday > (leap ? 366 : 365)){
    return;
  }
  for (i = 1; yearday > daytab[leap][i]; i++){
    yearday -= daytab[leap][i];
    *pmonth = i + 1;
    *pday   = yearday;
  }
}

void assert_month_day(
    int expected_month,
    int expected_day,
    int actual_year,
    int actual_yearday
    )
{
  int pmonth = -1;
  int pday = -1;
  month_day(actual_year, actual_yearday, &pmonth, &pday);
  assert_equal_int(expected_month, pmonth);
  assert_equal_int(expected_day, pday);
}

void test_month_day(void)
{
  printf("month_day testing:\n");
  assert_month_day(2, 29, 1988, 60);
  assert_month_day(12, 31, 2007, 365);
  assert_month_day(12, 30, 2008, 365);
  printf("\tyearday error test\n");
  assert_month_day(-1, -1, 2007, 366);
  assert_month_day(-1, -1, 2008, 367);
}

int main(int argc, char *argv[])
{
  test_day_of_year();
  test_month_day();
  return 0;
}

Makefileには次のように書いておけばvimならコマンドモードで"!make %:r && ./%:r"とするだけでコンパイルして実行してくれます。環境によってはこれだけだとダメかもしれません。

CC = gcc
CFLAGS = -Wall
LDLIBS = cunit.c

-Wallにしておくと例題などのコードがそのままだと警告が出まくるのがちょっとめんどくさいです。もっと標準のテストフレームワークとかあるんだろうなぁ…。やっぱり最近の開発環境周りとかも含めたところに触れてある本とかも合わせて読まないと効率悪いかも。

プログラミング言語C 第2版 ANSI規格準拠

プログラミング言語C 第2版 ANSI規格準拠