上手なテスト駆動開発の進め方について
受付中
回答 4
投稿
- 評価
- クリップ 12
- VIEW 4,246

退会済みユーザー
現在RailsアプリケーションでRSpecを使いながらテスト駆動か初に挑戦しています。
幾つかテストを書いて思ったことを質問させていただきます。
(テストを導入したのはアプリケーションの機能をほとんど実装してからです)
始めからテストをかけない
今回は、実装がほとんど出来ている状態からテストを書いているので、テストコードをメソッドごとにかけるのですが。
本来であれば最初に書くはずだと思います。ですが最初に書いたコードでは、綺麗にメソッド分割できておらず、ほぼベタ書き状態のものが多い気がします。
そこからリファクタリングをして、メソッドの分割、その後、各メソッドのテストコードを記述というのが理想的なような気がします。
結合テストからするべき?(テスト=>実装の流れならば)
単体テストの場合ではクラス単位、メソッド単位で行うことが多いかと思います。
テストコードの中でクラスやメソッドを呼び出すということは、クラス名やメソッド名の変更、引数の有無など変更できなくなります。(その都度テストコードを書き換えるのならば良いのですが)
なので、結合テスト(ブラウザぽちぽちして、最終に得られる結果を期待するコードを書く)をすると、メソッドの分割や、名前の変更ができるようになるのではないでしょうか?
その後単体テストを記述し、ロジックの部分をリファクタリングするというのが良いのではないかと思います。
皆様の意見を聞かせて頂きたいです。
-
気になる質問をクリップする
クリップした質問は、後からいつでもマイページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
クリップを取り消します
-
良い質問の評価を上げる
以下のような質問は評価を上げましょう
- 質問内容が明確
- 自分も答えを知りたい
- 質問者以外のユーザにも役立つ
評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。
質問の評価を上げたことを取り消します
-
評価を下げられる数の上限に達しました
評価を下げることができません
- 1日5回まで評価を下げられます
- 1日に1ユーザに対して2回まで評価を下げられます
質問の評価を下げる
teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。
- プログラミングに関係のない質問
- やってほしいことだけを記載した丸投げの質問
- 問題・課題が含まれていない質問
- 意図的に内容が抹消された質問
- 過去に投稿した質問と同じ内容の質問
- 広告と受け取られるような投稿
評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。
質問の評価を下げたことを取り消します
この機能は開放されていません
評価を下げる条件を満たしてません
質問の評価を下げる機能の利用条件
この機能を利用するためには、以下の事項を行う必要があります。
- 質問回答など一定の行動
-
メールアドレスの認証
メールアドレスの認証
-
質問評価に関するヘルプページの閲覧
質問評価に関するヘルプページの閲覧
+7
まずは質問の回答から…
始めからテストをかけない
テストと実際のコードは同時に作成して、同時に育てるが正解だと思ってます。
結合テストからするべき?
単体テスト以下です。
私がやった限り、TDDのテスト用コードは常に更新しまくりです。
ロジック修正したい、テスト用コードも一緒に変えよう…は日常茶飯事です。
ドキュメントをロジックの実装と同時平行で作るのと同じような感じになります。
以下解説
TDDを初めて半年弱のクソザコナメクジです。
ようやく最近自分なりの型や成功事例ができてきて楽しくなってきました。
この体験から、自分の文章で書いてみました。
普段デバッグはどんな感じでされてますか?
私はもっぱらNode.jsなのでconsole.logですが、
質問者さんはRubyなので、開発中やデバッグ中はprintをあっちこっちに仕込んでるんじゃないかと思います。
TDDではそんなダサいことしません。
デバッグ用のprint文はあとで消すので二度手間になります。
でも、テストコードは消す必要がありません。
TDDに慣れれば消す必要がない大量のprint文に守られながら開発する仕組みが構築できます。
テストに慣れない内は大きなブラックボックスを作ってしまい、デバッグ用コードのお世話になるかと思いますが、
デバッグコードが書けないと分かれば工夫してホワイトになるように頑張るしかなくなります。
私も最近はTDDのやり方に少し慣れ、デバッグ用コードを埋め込む事が減ってきました。
さて、ココからが本題です。
ですが最初に書いたコードでは、綺麗にメソッド分割できておらず、ほぼベタ書き状態のものが多い気がします。
確かに最初は何が作りたいのか曖昧な状態なので、難しいかと思います。
こういうメソッドがいいかな?ああいうメソッドがいいかな?戻り値はInt/String/Hash?
こういう心変わりが常に側にあるのがプログラミングで、
より良い方法を思いつく度に、影響範囲をどう管理するのかが課題です。
TDDは常に最新のロジックを保てと、愚直な修正作業を徹底する管理思想です。
まずは非TDDから見ていきましょう。
- このメソッドの戻り値はStringだったけど、不便だからHashに変更しよう
- 頭で覚えている箇所や、ファイル検索して依存している箇所を書き換える
- 全ての箇所の実装が終わる
- 単体テストで確認しているとエラー・・・えっ、なんで?
- 漏れてる箇所見つけたわ、横並び項目何個あったっけ・・・?
- デバッグコードをアチラコチラに仕込んで、不具合を修正
- デバッグコードを消し忘れてレビュー時や結合テスト時に見つかって先輩から叱られる
4〜7のコンボなんてあるあr・・・ねーよwwwという感じですが、たまにあるんですよね。
もれなくフラグを回収してきた私としては、規模が大きいシステムや、システムの完成間近になればなるほど泣きを見る確率が上がるんじゃないかなと思ってます。
次にTDDを見ていきましょう。
- このメソッドの戻り値はStringだったけど、不便だからHashに変更しよう
- 変更箇所や影響範囲を元にテストコードを修正
- テストを実行してアサートエラーの出るエラー項目を割り出す
- 該当の項目を修正していく
わぁ、リスクが減った!まるで進○ゼミ!!…では終わらないのがTDDです。
TDDの2のコストは決して無視出来ません。
思いついた最善のロジックをソースコードに反映させる前に、テストコードから手を付けなければならない…これは今でも苦痛です。
これが原因でTDD否定派は多数居ます。
大抵4〜7のリスクを軽く見積もっていたり、TDDに夢みすぎで夢破れたりが主な理由だと思います。
TDDは単なるリスクヘッジとして同時並行でドキュメントを作る程度のものであり、
それ以上でもそれ以下でもないって感じですね。
放置しすぎると使えないゴミになるのでメンテが必要な所もドキュメントと似てますね。
上記2点のメリットを享受する為には、
テストは同時並行、もしくは先に作られている必要があります。
対象の実行用コードのクラスの最初のメソッドが完成するまでにはメンテを開始させたいですね。
最近の私はファイルを作ったタイミングで、
testディレクトリから始まる同じディレクトリ構造のファイルを作るように徹底しています。
もちろんテスト対象のファイルがディレクトリ移動する場合は、面倒でもテストファイルも移動させてます。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
+4
こんにちは。
(私は、自動テストはやってますが、テスト駆動開発をやったことはありませんのでその点は差し引いて見て下さい。)
まず、自動テストとテスト駆動開発は混乱しやすいですが、これらは別物です。
自動テストを実装することはたいへん好ましいと思います。ロジックを修正する際の安心感が段違いです。また、リファクタリングを頻繁に実施できるのでソースをきれいに保ちやすいです。
そして、ターゲットのプログラムより先に自動テストを記述するのがテスト駆動開発ですね。
以上を踏まえ、単体テストの自動化についての質問と感じました。
単体テストの自動化は行うべきかどうか? 行うべきならそれはいつか?ですね。
結論としては、私は細かいレベルの単体テストを自動化すると設計変更時の工数が増大するので、原則避けた方が良いと思います。
テストを自動化すると、設計変更しても仕様が変化しないことを保証する場合にたいへん有効です。同じテストであれば何度でも最小の工数で実行できますから。
従って、設計変更して仕様変更が発生しないレベルでテストを自動化すると開発効率が良くなります。
逆に、細かい単位では設計変更すると仕様も一緒に変化する方が多いです。そのような部分についてテストを自動化しても工数ばかり掛かって意味はほとんどないように感じます。
もっとも重要なテストは、顧客へ約束している通りに動作していることを検証する機能テストです。その効率を上げるために有効なことはどしどしやるべきですが、逆に足を引っ張るようなことは避けた方が良いと思います。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
+2
むしろユニットテストを結合テスト前に実施した方がコードの品質を保ちやすい気がします。
ユニットテストでは、配列の個数や、メソッド名やクラス名を指定したアサーションを行うわけですから、名前の変更や渡される配列の個数が変われば、それでもうテストはエラーになります。
先に結合やってしまうと、人間が見る分にはエラーはでていないが、全然裏側で整合性がとれてなかったみたいな結果になりかねないと思われます。
テストもしょせん人が書くのが、テスト自体が間違っているとか観点が抜けていることはよくありますし、毎回テストを書くことでの恩恵を必ずしも受けられないのがデメリットかと思います。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
0
本来であれば最初に書くはずだと思います。ですが最初に書いたコードでは、綺麗にメソッド分割できておらず、ほぼベタ書き状態のものが多い気がします。
そこからリファクタリングをして、メソッドの分割、その後、各メソッドのテストコードを記述というのが理想的なような気がします。
最初の出だしをどうしようか迷いますよね。
最初からメソッド分割することが大事だと思います。TDDの場合、ボトムアップ的に作りたくなりますが、後で分割方法が変わると手戻りが大きいです。
私の場合、テストとのバランスを取ります。
最初の段階は、ラフスケッチのようなコードを書きます。
詳細でなく、呼び出し側でこんなクラスにこんなメソッドがあれはきれいにかけそうという当たりを付けます。このコードはラフスケッチなので、とりあえず捨てます。(これを一気に動作可能な状態にすると、妙に細かい部分があるので、TDDは無理です。)
それに沿って、コアになるモジュールの最小機能から設計します。
例えば、ストレージに文字列を登録するモジュールだったら、文字列を送ったら(実際に登録などせず)正常終了するようなテストを書きます。
あるいは、DBから文字列を取得するというコードであればどのようなキー(もしくはNull)を送っても、DBに問い合わせもせず一定の文字列を返すとかそういったテストです。
大事なのは、内部動作は気にせず、こういうインターフェイスがあったらいいなという考えで作成することです。
これができれは、そこから育てるのはそれほど難しくないと思います。コードを育てるのは手を動かす量が圧倒的に増えるのでコーディングに時間がかかのではないかという恐れがあると思います。
実際は、先ほどラフスケッチと呼んだコードを詳細まで書き上げた場合、すでに動作検証済みの部分の変更を避けるために、悪い設計をした部分のバグフィックスに大量の時間を割くことになります。
これがテストコードも育てるという考えでやった場合、検証済みのコードはテストコードが守ってくれます。変更が必要なテストコードは、テスト項目は変わりません。そのテスト項目がまったく不要になることがあるとすれば、それは初めには思いつかなかった素晴らしい設計を発見したとであり、それこそがTDDの最大の成果ではないでしょうか。
とはいえ、私も追加すべきコードが初めからわかるような単純なものに関してはTDDでは書かないこともおおいです。やはり、必要に応じてということも忘れてはならないことだと思います。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
15分調べてもわからないことは、teratailで質問しよう!
- ただいまの回答率 88.11%
- 質問をまとめることで、思考を整理して素早く解決
- テンプレート機能で、簡単に質問をまとめられる
2017/02/06 21:33
> 大抵4〜7のリスクを軽く見積もっていたり、TDDに夢みすぎで夢破れたりが主な理由だと思います。
少なくとも私の場合の主な理由ですが、中々取れないバグは単体テストでは検出できないエラー(複数のモジュール間の不整合)です。なので単体テストを頑張っても仕方がないかなって思いですね。
2017/02/06 23:22
例まで頂いて非常にわかりやすかったです。
> 思いついた最善のロジックをソースコードに反映させる前に、テストコードから手を付けなければならない…これは今でも苦痛です。
この辺はまさに思っていた通りで安心しました。これはやはり皆さん思っているのですね。
その分その後恩恵を受けられるのでしょうけど....。
ありがとうございました。
2017/02/14 12:52
複数のモジュール間の不整合に対応できないから単体テストをゆるくするは本末転倒な気がします。
2017/02/14 13:26 編集
私の回答はこの記事の主張に強く影響されています。
(私の考える)TDDで作るテストはCLIで実行出来る簡素なルールブック以上の価値はなく、速度を落とさず開発する為にガバガバな方が良いと考えています。
本音は品質も上げたいのですが、何を作れば良いか定まりきっていない中で欲張って厳格なルールを作って、こんなはずじゃなかった動かない迂回策を使わねば…えっテストこんなに修正すんの!?という負けパターンに陥って辛くなって断念するという失敗を重ねました。
今はガバガバなルールをさっさと作るという方針を打ち立てて動いています。
その作戦に則って考えると、副作用だらけで時間の掛かるテストはしない。
副作用の出る箇所と出ない箇所を分離して副作用のない箇所に絞ってテストを作るのはテクニックとしては有りと考えています。
カバレッジや品質を保証する単体テストや結合テストのフェーズでは、必要に応じて別の機会を設けるべきかと思います。
ただ、簡単でガバガバとはいえルールに全て準拠しているので、何もしないで単体テストに行くよりかは非常に高品質なものが出来るかと思います。
2017/02/14 18:01 編集
「 大抵4〜7のリスクを軽く見積もっていたり」がTDD否定派の見解ではないか?と書かれていたので、少なくとも私はモジュール間不整合の方が4~7より大きなリスクと感じているので、これを回避できない割に工数がかかる単体テストの自動化には否定的との記載です。
単体テスト自体はもちろん否定しません。単体テストを自動化することを否定しています。投入可能な工数は常に限りがありますから、単体テストが必要な時は手動で行い、その自動化の工数は機能テストの自動化に割いた方が全体の信頼性は上がると考えています。
miyabi-sunさん。
リンク先みました。なるほど、私はTDDを誤解していたようです。
TDDで記述する自動テストは信頼性保証テストではないのですね。なるほど。
確かに、ガバガバな自動化テストはコストパフォーマンスは良さそうな印象です。コーディング~デバック工程のアウトプットの信頼性を上げることで正規の単体テストからの手戻りが減り、その結果、全体の信頼性が上がるということですね。(一次不良率を下げることで全体の信頼性が上がると聞きます。)
ウォーターフォール型開発には有用な手法と思います。
開発速度命のアジャイル型には適用しない方が良いかも?
いや、テスト機能も持つスタブを先に開発すると言う意味ならアジャイル型にも有用かも知れないですね。
2017/02/24 11:50
返信ありがとうございます。
単体テストと機能テストそれぞれ自動化したほうがいいと思いますが潤沢に工数がない以上、「どこをがんばれば何をなしえるか」これに付きますね。私の感覚だと機能テストも紐解けば最小のクラスやメソッドにいきつくのでやはり、自動化するなら単体テストかな。と思ってしまいます。
また、ブラウザーを操作するようなテストも自動化対象と考えています。
機能テストはどうでしょうか。私の中でモジュール間不整合はViewとModelのI/F差異ぐらいしか浮かびませんでした。
Chironianさんの意見を聞けてよかったです。ありがとうございます。