TDDのテスト駆動


TDDはコードの前にテストを書く練習です、そして、それはあなたのソフトウェアの故障率と欠陥を減らすべきです.
このブログの記事では、どのように機能するかを示します.

出発点
私は、グレゴリオ日付とその逆にBkram Sambat(BS)(Vikram Samvatと呼ばれている)日付を変えるべきである試みで、アプリケーションを書いています.Vikram Samvat カレンダーは主にネパールとインドで使用されます.しかし、あなたがそれを使わないとしても、このデモンストレーションはTDDを理解するのに役に立つかもしれません.
これまでのところ、私はBBS(Bikram Sambat)の日付のインスタンスを作成し、その詳細を取得し、グレゴリオ暦の日付に変換することができます.参照:https://github.com/JankariTech/GoBikramSambat/blob/b99c510b22faf8395becda9a6dec1d0239504bb1/bsdate.go
これらの関数もテストされる:https://github.com/JankariTech/GoBikramSambat/blob/b99c510b22faf8395becda9a6dec1d0239504bb1/bsdate_test.go
今、私はグレゴリオの日付をBikram Sambat日付に変える可能性を加えたいです.そのためには、グレゴリオ暦を使ってBS dateインスタンスを作成することができます.
好きなものnepaliDate, err := NewFromGregorian(gregorianDay, gregorianMonth, gregorianYear) は素晴らしいし、単に既存のnepaliDate.GetDay() nepaliDate.GetMonth() and nepaliDate.GetYear()
1 .テストの作成
TDDによると、まずテストをしなければなりません.
このようにbsdate_test.go 新しい関数を作成しますTestCreateFromGregorian() .
すでにネパールからグレゴリオ暦への変換に使用されるテスト日付のテーブルを持っているので、逆変換をテストするためにもそのデータを使用します.
テストデータとテスト関数を示します.
type TestDateConversionStruc struct {
    bsDate        string
    gregorianDate string
}

var convertedDates = []TestDateConversionStruc{
    {"2068-04-01", "2011-07-17"}, //a random date
    {"2068-01-01", "2011-04-14"}, //1st Baisakh
    {"2037-11-28", "1981-03-11"},
    {"2038-09-17", "1982-01-01"}, //1st Jan
    {"2040-09-17", "1984-01-01"}, //1st Jan in a leap year
...
}

func TestCreateFromGregorian(t *testing.T) {
    for _, testCase := range convertedDates {
        t.Run(testCase.bsDate, func(t *testing.T) {
            var splitedBSDate = strings.Split(testCase.bsDate, "-")
            var expectedBsDay, _ = strconv.Atoi(splitedBSDate[2])
            var expectedBsMonth, _ = strconv.Atoi(splitedBSDate[1])
            var expectedBsYear, _ = strconv.Atoi(splitedBSDate[0])

            var splitedGregorianDate = strings.Split(testCase.gregorianDate, "-")
            var gregorianDay, _ = strconv.Atoi(splitedGregorianDate[2])
            var gregorianMonth, _ = strconv.Atoi(splitedGregorianDate[1])
            var gregorianYear, _ = strconv.Atoi(splitedGregorianDate[0])

            nepaliDate, err := NewFromGregorian(gregorianDay, gregorianMonth, gregorianYear)
            assert.Equal(t, err, nil)
            assert.Equal(t, nepaliDate.GetDay(), expectedBsDay)
            assert.Equal(t, nepaliDate.GetMonth(), expectedBsMonth)
            assert.Equal(t, nepaliDate.GetYear(), expectedBsYear)
        })
    }
}
この関数はconvertedDates リストを分割して、特定のグレゴリオテストケースからBS日付を作成しようとし、BS日付(日、月、年)が予想通りであることをアサートします.

2 .テストの実行
テストは、TDDによると、私はそれを実行する必要があります.go test -v結果
# NepaliCalendar/bsdate [NepaliCalendar/bsdate.test]
./bsdate_test.go:171:23: undefined: NewFromGregorian
FAIL    NepaliCalendar/bsdate [build failed]
それは期待された、機能は存在しません、私のテストが失敗するのも不思議ではありません.次に何をするか推測:関数の実装.
それはTDDので簡単に、あなただけのテストを修正する方法を教えてください.

それを修正
それは簡単ですbsdate.go 新しい機能:
func NewFromGregorian(gregorianDay, gregorianMonth, gregorianYear int) (Date, error) {

}

繰り返す
もう一度テストを実行する./bsdate.go:195:1: missing return at end of functionそれでは、何かを返しましょうか?ちょっとグレゴリオ数字でBS日付を作成しましょう
 func NewFromGregorian(gregorianDay, gregorianMonth, gregorianYear int) (Date, error) {
-
+       return New(gregorianDay, gregorianMonth, gregorianYear)
 }
あなたはそれが動作しないと言っている?私は気にしない、私はTDDを行う、テストは何かを返すように指示し、私は戻って、私も正しい値の値を返します.
もう一度テストを実行します.
=== RUN   TestCreateFromGregorian/2068-04-01
    assert.go:24: got '17' want '1'

    assert.go:24: got '7' want '4'

    assert.go:24: got '2011' want '2068'

=== RUN   TestCreateFromGregorian/2068-01-01
    assert.go:24: got '14' want '1'

    assert.go:24: got '4' want '1'

    assert.go:24: got '2011' want '2068'

....
多くの失敗、あなたはそれを推測して、変換は動作しません.したがって、いくつかのビットを実装します.
我々はBSがグレゴリオ暦の何年も前に56ポイントであることを知っている.したがって、グレゴリオ暦年に56を加えることは助けなければなりません.
 func NewFromGregorian(gregorianDay, gregorianMonth, gregorianYear int) (Date, error) {
-       return New(gregorianDay, gregorianMonth, gregorianYear)
+       var bsYear = gregorianYear + 56
+       return New(gregorianDay, gregorianMonth, bsYear)
 }
テスト結果は
....
=== RUN   TestCreateFromGregorian/2037-11-28
    assert.go:24: got '11' want '28'

    assert.go:24: got '3' want '11'

    assert.go:24: got '1981' want '2037'

=== RUN   TestCreateFromGregorian/2038-09-17
    assert.go:24: got '1' want '17'

    assert.go:24: got '1' want '9'

    assert.go:24: got '1982' want '2038'
....
私は得る
....
=== RUN   TestCreateFromGregorian/2037-11-28
    assert.go:24: got '11' want '28'

    assert.go:24: got '3' want '11'

=== RUN   TestCreateFromGregorian/2038-09-17
    assert.go:24: got '1' want '17'

    assert.go:24: got '1' want '9'
....
少なくとも数年は正確に計算されます.より正確な年を計算することによってより多くのテストを修正し、また、BSの月を計算することができます.
BSカレンダーの作品のため、アルゴリズムがないグレゴリオ暦から直接日付を変換するために、我々はテーブルが必要です.私たちは1月1日は常に第9回BS月(Paush)で知っている.それで、私たちは、最初の価値がその年の1月1日であるPaushの日であり、それから、すべてのBS月の日のリストであるBS年のテーブルを持っています.
我々は簡単にグレゴリオ暦から一日を得ることができます.Paushから始めて、我々は各BS月の日を数えます、我々が年のグレゴリオ暦日を乗り越えるときはいつでも、我々は正しいBS月を見つけました.
2074: [13]int{17, 31, 31, 31, 32, 31, 31, 30, 29, 30, 29, 30, 30},
2075: [13]int{17, 31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30},
2076: [13]int{16, 31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 30},
2077: [13]int{17, 31, 32, 31, 32, 31, 30, 30, 30, 29, 30, 29, 31},
2078: [13]int{17, 31, 31, 31, 32, 31, 31, 30, 29, 30, 29, 30, 30},
これらの詳細は、TDDとは関係ありませんが、今後のアルゴリズムを理解するのに役立ちます.
コード化します
 func NewFromGregorian(gregorianDay, gregorianMonth, gregorianYear int) (Date, error) {
        var bsYear = gregorianYear + 56
-       return New(gregorianDay, gregorianMonth, bsYear)
+       var bsMonth = 9                         //Jan 1 always fall in BS month Paush which is the 9th month
+       var daysSinceJanFirstToEndOfBsMonth int //days calculated from 1st Jan till the end of the actual BS month,
+                                               // we use this value to check if the gregorian Date is in the actual BS month
+
+       year := time.Date(gregorianYear, time.Month(gregorianMonth), gregorianDay, 0, 0, 0, 0, time.UTC)
+       var gregorianDayOfYear = year.YearDay()
+
+       //get the BS day in Paush (month 9) of 1st January
+       var dayOfFirstJanInPaush = calendardata[bsYear][0]
+
+       //check how many days are left of Paush
+       daysSinceJanFirstToEndOfBsMonth = calendardata[bsYear][bsMonth] - dayOfFirstJanInPaush + 1
+
+       //If the gregorian day-of-year is smaller or equal to the sum of days between the 1st January and
+       //the end of the actual BS month we found the correct nepali month.
+       //Example:
+       //The 4th February 2011 is the gregorianDayOfYear 35 (31 days of January + 4)
+       //1st January 2011 is in the BS year 2067 and its the 17th day of Paush (9th month)
+       //In 2067 Paush had 30days, This means (30-17+1=14) there are 14days between 1st January and end of Paush
+       //(including 17th January)
+       //The gregorianDayOfYear (35) is bigger than 14, so we check the next month
+       //The next BS month (Mangh) has 29 days
+       //29+14=43, this is bigger than gregorianDayOfYear(35) so, we found the correct nepali month
+       for ; gregorianDayOfYear > daysSinceJanFirstToEndOfBsMonth; {
+               bsMonth++
+               if bsMonth > 12 {
+                       bsMonth = 1
+                       bsYear++
+               }
+               daysSinceJanFirstToEndOfBsMonth += calendardata[bsYear][bsMonth]
+       }
+
+       return New(gregorianDay, bsMonth, bsYear)
 }
そして今?あなたはそれを推測!テストを実行します.
=== RUN   TestCreateFromGregorian
=== RUN   TestCreateFromGregorian/2068-04-01
    assert.go:24: got '17' want '1'

=== RUN   TestCreateFromGregorian/2068-01-01
    assert.go:24: got '14' want '1'

=== RUN   TestCreateFromGregorian/2037-11-28
    assert.go:24: got '11' want '28'

....
実際、アルゴリズムを実装している間、テストを複数回実行して、変数名やその他のゴミに混乱を見つけました.それはクールです、テストは私がすぐに問題を見つけるのを助けました.
しかし、テストはまだ失敗し、私はより良い日計算を得る.
我々は正しいBS月を知っています、そして、我々は今月の終わりまで今月1月から日を知っています.月1日から、正しいBS月の終わりまでグレゴリオ暦の年の日を差し引くことは、我々に検索された日とBS月の終わりの間の日の量を与えます.BSの月の日の量からその数を減算すると、正しい日付に私たちをもたらす必要があります.
多くの単語を記述するため、コードで書く努力はほとんどありません.
-       return New(gregorianDay, bsMonth, bsYear)
+       var bsDay = calendardata[bsYear][bsMonth] - (daysSinceJanFirstToEndOfBsMonth - gregorianDayOfYear)
+
+       return New(bsDay, bsMonth, bsYear)
テストを実行してテストを実行します心配しないでください.
=== RUN   TestCreateFromGregorian
=== RUN   TestCreateFromGregorian/2068-04-01
=== RUN   TestCreateFromGregorian/2068-01-01
=== RUN   TestCreateFromGregorian/2037-11-28
=== RUN   TestCreateFromGregorian/2038-09-17
=== RUN   TestCreateFromGregorian/2040-09-17
=== RUN   TestCreateFromGregorian/2040-09-18
=== RUN   TestCreateFromGregorian/2041-09-17
=== RUN   TestCreateFromGregorian/2041-09-18
=== RUN   TestCreateFromGregorian/2068-09-01
=== RUN   TestCreateFromGregorian/2068-08-29
=== RUN   TestCreateFromGregorian/2068-09-20
=== RUN   TestCreateFromGregorian/2077-08-30
=== RUN   TestCreateFromGregorian/2077-09-16
=== RUN   TestCreateFromGregorian/2074-09-16
=== RUN   TestCreateFromGregorian/2077-09-17
=== RUN   TestCreateFromGregorian/2077-09-01
=== RUN   TestCreateFromGregorian/2076-11-17
=== RUN   TestCreateFromGregorian/2076-11-18
=== RUN   TestCreateFromGregorian/2075-11-16
=== RUN   TestCreateFromGregorian/2076-02-01
=== RUN   TestCreateFromGregorian/2076-02-32
=== RUN   TestCreateFromGregorian/2076-03-01
--- PASS: TestCreateFromGregorian (0.00s)
    --- PASS: TestCreateFromGregorian/2068-04-01 (0.00s)
    --- PASS: TestCreateFromGregorian/2068-01-01 (0.00s)
    --- PASS: TestCreateFromGregorian/2037-11-28 (0.00s)
    --- PASS: TestCreateFromGregorian/2038-09-17 (0.00s)
    --- PASS: TestCreateFromGregorian/2040-09-17 (0.00s)
    --- PASS: TestCreateFromGregorian/2040-09-18 (0.00s)
    --- PASS: TestCreateFromGregorian/2041-09-17 (0.00s)
    --- PASS: TestCreateFromGregorian/2041-09-18 (0.00s)
    --- PASS: TestCreateFromGregorian/2068-09-01 (0.00s)
    --- PASS: TestCreateFromGregorian/2068-08-29 (0.00s)
    --- PASS: TestCreateFromGregorian/2068-09-20 (0.00s)
    --- PASS: TestCreateFromGregorian/2077-08-30 (0.00s)
    --- PASS: TestCreateFromGregorian/2077-09-16 (0.00s)
    --- PASS: TestCreateFromGregorian/2074-09-16 (0.00s)
    --- PASS: TestCreateFromGregorian/2077-09-17 (0.00s)
    --- PASS: TestCreateFromGregorian/2077-09-01 (0.00s)
    --- PASS: TestCreateFromGregorian/2076-11-17 (0.00s)
    --- PASS: TestCreateFromGregorian/2076-11-18 (0.00s)
    --- PASS: TestCreateFromGregorian/2075-11-16 (0.00s)
    --- PASS: TestCreateFromGregorian/2076-02-01 (0.00s)
    --- PASS: TestCreateFromGregorian/2076-02-32 (0.00s)
    --- PASS: TestCreateFromGregorian/2076-03-01 (0.00s)
PASS
ok      NepaliCalendar/bsdate   0.002s
テスト合格、仕事完了!それをコミット、プッシュして自分自身をいくつか!
この投稿のすべての変更をここで見つけることができます.https://github.com/JankariTech/GoBikramSambat/pull/4/

結論
TDDは簡単です:あなたが達成するために何を達成するために、それをテストし、あなたのテストが通るまで乱暴にハックコードを書いてください.
他の大きな利点は:私は私が好きなすべての私のコードをリファクタリングすることができますし、まだそれがうまく動作する自信があります.たぶん私はアルゴリズムの速度を最適化したい、多分私はそれを完全に好きではない、より良いものを思い付く、または私は単に変数名を変更したい.私は、私のテストが通過している限り、私はかなりコードが同じ反応することを確信しています.

多分次のステップ
ソフトウェア開発における他の有用な原理は、TDDから出てきて、一般的な原理を使用しているが、単一の単位(機能)を定義し、テストするのではなく、システムの振る舞いを記述し、プロジェクトの異なるステークホルダー間のコミュニケーションを改善することに焦点を当てている.私は、同じプロジェクトを使用してBDDについてのポストを書きました.