お題
「TDD(テスト駆動開発)」で開発したい。
そう思うものの、実際にはなかなか実現できる現場を経験する機会がない。
ある程度の裁量があり、許可を得た上で個人的に担当機能をTDDで実装したことはあるものの、チームとして浸透させることは出来ず(何名かは同意して実践してくれたものの)、最終的に現場の混乱を招いた。。。
TDDを始めようと提案した際(ないし、実施中、実施後)に反対(批判)された理由はおおよそ以下の通り。
(ネット上の記事でもよく出てくる理由と、まあ同じ感じかな)
- 実装に時間がかかり過ぎる。(それゆえ工数見積りも膨れ、顧客OKが出ない)
- 仕様がころころ変わり、そのつどテストコードとプロダクトコードともに直す必要があって(やはり)時間がかかる。
- 「テストコード書いても、(画面もあるから)実装終わったら、またテストするでしょ?」(二度手間では?)
- テストコードも人が書くものだからプロダクトコードと同じでバグ作ることになる。
上記それぞれに反論はあるのだけど、そして実際現場では力説してみたのだけど、響かなかった。
まあ、これは多分に自分の説得力の無さが問題ではあるけど・・・。
(何しろ、上記のデメリット(?)の部分を補って余りあるメリットを(例えば数値として)示せなかったのだから。)
問題は、まだある。テストコードを書くにも相応のスキルが必要なのだ。
前述の”混乱”には(自分も含めて)テストコードを書くスキルが足りなかったがために、逆に以下のようなテストコードが負債となる結果を招いてしまった。
- モックを使うケース使わないケースが入り乱れた。
- 変にカバレッジを意識し過ぎてテストケースの粒度が細かくなりすぎた。
- goroutine使用機能でタイミングによりパスしないテストケースが出てきた。
- 外部依存の多い機能のテストケースで事前準備コードが肥大化した。
こうした経験は、多くの気付きを与えてくれて、結果、スキルアップにつながった(はず)のだけど、やはり現場で取り入れるには大きな壁がある。
主に、イニシャルコスト(どうしたって、ただ実装するより時間はかかる。(あくまで”コードを書く”という点のみで考えるとだけど。))とメンテナンスコスト(仕様変更のたびにまずテストコードから直してプロダクトコード直すから。)がかかることが顧客の理解を得づらいのだ。
実際、顧客にも以下のように言われたことがある。
「今まで、画面から動かして確認するテストだけの工数でちゃんとやってくれたのに、なんで同じことやるのに追加でこんなに工数も期間もかかることしないといけないの?」
「テストコード書いてからプログラム書くのと、プログラム書いてそれを(今まで通り)画面から動かしてテストするのと、どちらが早く終わる? 早い方にして。」
(目先の)『早い』、『安い』の観点で語られると、(少なくとも経験上)TDDに勝ち目は無い。
上記で語られないTDDの最も大きな(と個人的に思っている)メリットは「ソフトウェア(サービス)の品質を保ち、メンテナンスコストの上昇を抑え、ソフトウェアの寿命を長くすること」だと思う。
* ※逆に言うと、せいぜい1〜2年くらいで畳んでしまってもいいサービスなら、コスト見合いで諦めた方がいいのかもしれない。*
* エンジニア目線では絶対に嫌だけど、そこは経営者判断、顧客判断の問題なので。*
ソフトウェア(サービス)は、初期の作りの出来によって、仕様変更や機能追加、バグ修正を行う際のコストに大きく差が出る。
それは、長く使えば使うほど顕著になる。
良い出来の方ならもちろんいいのだけど、悪い出来の方になると、そこに関わるエンジニアのモチベーションにも悪い影響が出て、エンジニアの確保にも影響が出る。
こうした事態を防ぐために、TDDは大きく貢献できるはず。と、信じているのだけど、実際は、やはり実践するところまでなかなか持っていけずにいる。
そうして、もやもやしていた中、「BDD(振る舞い駆動開発)」を思い出した。
そして、Goでテストコードを書いていた時もちょっと気になっていた「Ginkgo」のことも思い出したので、(銀の弾丸になるとまでは言えないかもしれないけど)試してみようと思った。
開発環境
# OS
$ cat /etc/os-release
NAME="Ubuntu"
VERSION="18.04.2 LTS (Bionic Beaver)"
# Golang
$ go version
go version go1.11.4 linux/amd64
実践
導入
$ go get github.com/onsi/ginkgo/ginkgo
go: finding github.com/onsi/ginkgo/ginkgo latest
go: finding github.com/onsi/ginkgo v1.8.0
go: downloading github.com/onsi/ginkgo v1.8.0
go: downloading github.com/hpcloud/tail v1.0.0
go: finding gopkg.in/tomb.v1 latest
go: downloading gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7
go: downloading gopkg.in/fsnotify.v1 v1.4.7
$ go get github.com/onsi/gomega/...
go: finding github.com/onsi/gomega/... latest
go: finding github.com/onsi/gomega v1.5.0
go: downloading github.com/onsi/gomega v1.5.0
go get github.com/onsi/gomega/...: no matching versions for query "latest"
ひな型作成
$ pwd
/home/sky0621/work/src/go111/src/github.com/sky0621/tips-go/tools/ginkgo
$
$ ginkgo bootstrap
Generating ginkgo test suite bootstrap for ginkgo in:
ginkgo_suite_test.go
$
$ ls -l
total 4
-rw-r--r-- 1 sky0621 sky0621 191 Apr 26 08:55 ginkgo_suite_test.go
package ginkgo_test
import (
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func TestGinkgo(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Ginkgo Suite")
}
ひな型実行
$ go test -v .
=== RUN TestGinkgo
Running Suite: Ginkgo Suite
===========================
Random Seed: 1556236617
Will run 0 of 0 specs
Ran 0 of 0 Specs in 0.000 seconds
SUCCESS! -- 0 Passed | 0 Failed | 0 Pending | 0 Skipped
--- PASS: TestGinkgo (0.00s)
PASS
ok tips-go/tools/ginkgo 0.004s
まとめ
ダラダラ長い序文を書いているうちに時間切れになったので、続きは次回の実践編に。