SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事

Visual Studio 11 : C++ Unit Test Framework
── C++単体テストの決定版(かもしれない)

Windows環境下でのC++単体テストはこれでキマリ!?


  • X ポスト
  • このエントリーをはてなブックマークに追加

"こう動いてほしい"をテストに書く

 プログラムは思ったとおりには動きません、作ったとおりに動きます。思ったとおりに動かすには、思ったとおりに作らなければなりません。Unit Test Frameworkは思ったとおりに作ることを手助けしてくれます。Test First/TDD(Test Driven Development:テスト駆動開発)では:

  • こう動いてほしいをテストに書く
  • テストに成功するように作り、テストを実行して検証する。

 この2つを繰り返します。あなたの思いがもれなく/正しくテストに書かれ、そのすべてが成功すれば思ったとおりに作ったことになります。その後変更や拡張の必要が生じたら、そのたびにテストを加筆修正して思いをしたため、テスト結果がAll-Greenとなるまでコードをいじくることで常に思ったとおりに動く状態を維持します。テストコードにこう動いてほしいという思いをぶつけましょう。

Test/DateTest.cpp
#include "stdafx.h"
#include "CppUnitTest.h"
#include <orz/Date.h>

using namespace Microsoft::VisualStudio::CppUnitTestFramework;
using namespace orz;

namespace orzTest {

  TEST_CLASS(DateTest) {
  public:
    TEST_METHOD(constructor) {
      Date date(1,2,3);
      Assert::AreEqual(1, date.year());
      Assert::AreEqual(2, date.month());
      Assert::AreEqual(3, date.day());
      Date date2(date);
      Assert::AreEqual(1, date2.year());
      Assert::AreEqual(2, date2.month());
      Assert::AreEqual(3, date2.day());
    }

    TEST_METHOD(copy) {
      Date src(1,2,3);
      Date date;
      date = src;
      Assert::AreEqual(1, date.year());
      Assert::AreEqual(2, date.month());
      Assert::AreEqual(3, date.day());
      date = Date(4,5,6);
      Assert::AreEqual(4, date.year());
      Assert::AreEqual(5, date.month());
      Assert::AreEqual(6, date.day());
    }

    // 一年未満はエラーになる
    TEST_METHOD(bad_year) {
      Date date;
      Assert::IsFalse(date.set(0,1,1).is_valid());
    }

    // 一月から十二月までならよいけれど
    TEST_METHOD(good_year_within) {
      Date date;
      Assert::IsTrue(date.set(2002,  1, 3).is_valid());
      Assert::IsTrue(date.set(2003, 12, 4).is_valid());
    }

    // 一月未満はエラーだし
    TEST_METHOD(bad_month_lt0) {
      Date date;
      Assert::IsFalse(date.set(2004, 0, 5).is_valid());
    }

    // 十二月を超えた場合もエラーだ
    TEST_METHOD(bad_month_gt12) {
      Date date;
      Assert::IsFalse(date.set(2005,13, 6).is_valid());
    }

    // 同様に一日未満はエラーだ
    TEST_METHOD(bad_day_lt0) {
      Date date;
      Assert::IsFalse(date.set(2006, 3, 0).is_valid());
    }

    // 小の月の三十日までや大の月の三十一日までならよいけれど
    TEST_METHOD(goos_days_in_month) {
      Date date;
      Assert::IsTrue(date.set(2007, 4,30).is_valid());
      Assert::IsTrue(date.set(2008, 3,31).is_valid());
    }

    // 小の月の三十日を超えた場合はエラーで
    TEST_METHOD(bad_days_in_month_1) {
      Date date;
      Assert::IsFalse(date.set(2009, 9,31).is_valid());
    }

    // 大の月だって三十一日を超えちゃったらエラーなのだ
    TEST_METHOD(bad_days_in_month_2) {
      Date date;
      Assert::IsFalse(date.set(2010, 8,32).is_valid());
    }

    // んでもって年が4で割り切れる年は原則うるう年なので二月二十九日は大丈夫で
    TEST_METHOD(good_days_in_feb) {
      Date date;
      Assert::IsTrue(date.set(2004, 2,29).is_valid());
      Assert::IsFalse(date.set(2005, 2, 29).is_valid());
    }

    // うるう年なら二月三十日はアウト
    TEST_METHOD(bad_days_in_feb_on_leap) {
      Date date;
      Assert::IsFalse(date.set(2004, 2,30).is_valid());
    }

    // ただし年が100で割り切れちゃったらうるう年じゃない
    TEST_METHOD(not_leap_on_div_100) {
      Date date;
      Assert::IsFalse(date.set(2100, 2, 29).is_valid());
    }

    // でも年が400で割り切れたらうるう年
    TEST_METHOD(leap_on_div_400) {
      Date date;
      Assert::IsTrue(date.set(2000, 2, 29).is_valid());
      Assert::IsTrue(date.set(2400, 2, 29).is_valid());
    }
  };

}

 ...これで十分でしょうか。単体テストエクスプローラからすべて実行すると、当然のことながら正しい日付を期待するテストはまっかっかです。

 1つずつ、RedがGreenになるよう、Date.h/Date.cppに手を加えてはテストを繰り返してください。すべてのテストに成功すれば実装完了です。

 テストの完了したコードを以下に示します。

Target/ionclude/orz/Date.h
#ifndef ORZ_DATE_H__
#define ORZ_DATE_H__

namespace orz {

  class Date {
  public:
    Date();
    Date(const Date&);
    Date(int year, int month, int day);
    Date& operator=(const Date&);
    Date& set(int year, int month, int day);
    int   year() const;
    int   month() const;
    int   day() const;
    bool  is_valid() const; // 日付として正しい?

  private:
    bool valid_year() const;    // 正しい年?
    bool valid_month() const;   // 正しい月?
    bool valid_day() const;     // 正しい日?
    int  days_in_month() const; // その月の日数 
    int  days_in_feb() const;   // その年の二月の日数
    bool is_leap() const;       // その年は閏年?

    int year_;
    int month_;
    int day_;
  };
}
#endif
Target/Date.cpp
#include <orz/Date.h>

namespace orz {

  Date::Date() : year_(0), month_(0), day_(0) {}
  Date::Date(int year, int month, int day) 
    : year_(year), month_(month), day_(day) {
  }
  Date::Date(const Date& date) 
    : year_(date.year_), month_(date.month_), day_(date.day_) {
  }

  Date& Date::operator=(const Date& date) {
    if ( this != &date ) {
      year_ = date.year_;
      month_ = date.month_;
      day_ = date.day_;
    }
    return *this;
  }

  Date& Date::set(int year, int month, int day) {
    year_ = year;
    month_ = month;
    day_ = day;
    return *this;
  }

  int Date::year() const  { return year_;  }
  int Date::month() const { return month_; }
  int Date::day() const   { return day_;   }

  bool Date::is_valid() const {
    // 年/月/日が正しければ日付として正しい
    return valid_year() && valid_month() && valid_day();
  }

  bool Date::valid_year() const  {
    // 年は 1以上
    return year_ >= 1; 
  }

  bool Date::valid_month() const {
    // 月は 1以上 12以下
    return month_ >= 1 && month_ <= 12; 
  }

  bool Date::valid_day() const   {
    // 日は 1以上 その月の日数以下
    return day_ >= 1 && day_ <= days_in_month();
  }

  int Date::days_in_month() const {
    // その月の日数
    switch( month_ ) {
    // 二月
    case 2: return days_in_feb();
    // 小の月
    case 4: case 6: case 9: case 11: return 30;
    // 大の月
    default: return 31;
    }
  }

  int Date::days_in_feb() const {
    // 二月は 閏年なら29日 さもなくば 28日
    return is_leap() ? 29 : 28;
  }

  bool Date::is_leap() const {
    // 閏年は 4で割れて100で割れない または 400で割れる
    return (year_ % 4 == 0 && year_ % 100 != 0)
        || (year_ % 400 == 0); 
  }

}

次のページ
もう一人の助っ人:コード・カバレージ

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
特集記事連載記事一覧

もっと読む

この記事の著者

επιστημη(エピステーメー)

C++に首まで浸かったプログラマ。Microsoft MVP, Visual C++ (2004.01~2018.06) "だった"りわんくま同盟でたまにセッションスピーカやったり中国茶淹れてにわか茶...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/6464 2012/04/05 11:30

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング