TDD対生産性


131

現在のプロジェクト(C ++のゲーム)では、開発中にテスト駆動開発を100%使用することにしました。

コードの品質に関しては、これは素晴らしいことです。私のコードはこれほどうまく設計されていないか、バグがありません。プロジェクトの開始時に1年前に書いたコードを見るとき、私はしつこくありません。そして、より簡単にテストできるようにするだけでなく、実装と使用がより簡単になるように、物事を構造化する方法についてより良い感覚を得ました。

しかし...私がプロジェクトを始めてから1年が経ちました。確かに、空き時間にしか作業できませんが、TDDを使用すると、以前と比べてかなり遅くなります。開発の速度が遅くなると時間が経つにつれて良くなることを読み、以前よりもずっと簡単にテストを考え出すことは間違いありませんが、私は1年前からそれをやっていて、まだカタツムリのペースで働いています。

作業が必要な次のステップについて考えるたびに、毎回停止して、実際のコードを書くことができるように、テストをどのように書くかを考えなければなりません。書きたいコードを正確に知っているが、テストで完全にカバーできるほど細かく分解する方法が分からず、何時間も動けなくなることがあります。それ以外の場合は、すぐに12個のテストを考え、テストを書くのに1時間を費やして、さもなければ書くのに数分かかるであろう実際のコードの小さな断片をカバーします。

または、ゲーム内の特定のエンティティとその作成と使用のすべての側面をカバーするために50回目のテストを終了した後、To Doリストを見て、コーディングする次のエンティティを確認し、執筆の思考に恐怖を覚えます実装するための別の50の同様のテスト。

昨年の進捗状況を見ながら、「いまいましいプロジェクトを終わらせる」ためにTDDを放棄することを検討しています。ただし、付属のコード品質を放棄することは、私が楽しみにしていることではありません。テストの作成をやめると、コードをモジュール化してテストしやすくする習慣から抜け出すのが怖いです。

私はおそらくこれで非常に遅いために何か間違ったことをしていますか?メリットを完全に失うことなく生産性を向上させる代替手段はありますか?TAD?テストカバレッジが少ない?すべての生産性と動機を損なうことなく、他の人々はどのようにTDDを生き延びますか?


@Nairou:いつでも「プロジェクトを完成させる」ことができます!今すぐブランチを作成します。そこにコードを書くだけです。ただし、時間またはゲームエンティティの数によって、実行する操作を制限し、速度が向上したかどうかを確認してください。その後、そのブランチを無視し、そこからトランクとTDDに戻って、その違いを確認できます。
クアムラナ

9
私にとって、テストの記述が早すぎるのは、最適化が早すぎるのに似ています。とにかく将来削除するコードのテストに一生懸命取り組んでいるかもしれません。
LennyProgrammers

コードをよりテストしやすいように設計する方法を考えるのに何時間も費やしていることに少し心配です。テスト容易性は、適切に設計された設計の属性である可能性がありますが、設計の最優先目標であってはなりません
ジェレミー

2
私が学んでいた頃、設計文書を提供しなければならなかったときの秘hadがありました。最初にコードを作成し、次にコードを説明するドキュメントを作成しました。たぶん、あなたはあなたのTDDのためにその実用主義の適度な量を学ぶ必要があります。計画をすでに考えている場合は、テストを作成する前に、そのほとんどをコードに入れる方が良いでしょう。理想主義が示唆するものが何であれ、時には他の何かに気を取られて、あなたがもう新鮮でないときに戻ってくるよりも、すでに準備ができていることをする方が良い場合があります。
Steve314

4
私は、一般的な意見に反して、ゲームを作成している場合、TDDが常に正しい選択であるとは限らないと言います。gamedev.stackexchangeの誰かが既にこの質問に見事に回答しているので、ここにリンクします
l46kok

回答:


77

あなたの経験を共有し、あなたの懸念を表明することに感謝することから始めましょう...私は言わなければならないことは珍しいことではありません。

  • 時間/生産性:テストを書くことは、テストを書かないことよりも遅いです。それに範囲を絞るなら、私は同意するでしょう。ただし、TDD以外のアプローチを適用する並列作業を実行する場合、break-detect-debug-and-fixの既存のコードを費やす時間がネットネガティブになる可能性があります。私にとって、TDDは、コードの信頼性を損なうことなく実行できる最速です。メソッドで何かを見つけたとしても、それが価値を高めていない場合は、それらを排除します。
  • テストの数:N個をコーディングする場合、N個をテストする必要があります。ケントベックの行の1つを言い換えると、動作させたい場合にのみテストする」。
  • 何時間も動けなくなる:私もやります(ラインを停止する20分以上前のこともあります)..それは、デザインに作業が必要であることを伝えるコードです。テストは、SUTクラスの別のクライアントです。テストでタイプを使用するのが難しいと判断された場合、実稼働クライアントも同様です。
  • 同様のテストtedium:これには、反論を書くためにもう少しコンテキストが必要です。つまり、停止して、類似性について考えてください。これらのテストを何らかの方法でデータ駆動できますか?ベースタイプに対するテストを書くことは可能ですか?次に、各派生に対して同じテストセットを実行する必要があります。テストを聞いてください。適切な怠け者になって、退屈を避ける方法を見つけられるかどうかを確認してください。
  • 次に行うべきこと(テスト/仕様)について考えるのをやめることは悪いことではありません。それどころか、「正しいこと」を構築することをお勧めします。通常、それをテストする方法が考えられない場合、通常は実装も考えられません。そこにたどり着くまで実装のアイデアを消すのは良い考えです。たぶん、YAGNIのようなプリエンプティブなデザインによって、よりシンプルなソリューションが隠れているかもしれません。

そして、それは私に最終的な質問に連れて行きます:どうすれば良くなりますか?私(またはAn)の答えは、読んで、考えて、練習することです。

例最近、私はタブをつけ続けます

  • 私のリズムがRG [Ref] RG [Ref] RG [Ref]を反映しているか、それともRRRRGRRefか。
  • 赤/コンパイルエラー状態で費やされた時間の割合。
  • Red / Brokenビルド状態で立ち往生していますか?

1
テストのデータ駆動に関するコメントに非常に興味があります。同様のコードを再テストするのではなく、外部データ(ファイルなど)を処理する単一のテストセットを意味しますか?私のゲームには複数のエンティティがあり、それぞれが大きく異なりますが、それらを使用して行われる一般的なことがいくつかあります(ネットワーク上でそれらをシリアル化し、存在しないプレーヤーに送信されないようにするなど)。これまでのところ、これを統合する方法が見つかりませんでした。したがって、それぞれが使用するエンティティと含まれるデータのみが異なる、ほぼ同一のテストセットがあります。

@Nairoi-使用しているテストランナーがわからない。伝えたいことの名前を知ったばかりです。抽象フィクスチャパターン[ goo.gl/dWp3k] 。これには、具象SUTタイプと同数のFixtureを記述する必要があります。さらに簡潔にしたい場合は、ランナーのドキュメントをご覧ください。例えば、NUnitは、パラメータ化された汎用テストフィクスチャをサポートしています(今では検索しました)goo.gl/c1eEQまさに必要なもののようです。
Gishu

興味深いことに、抽象的な備品について聞いたことがない。私は現在、フィクスチャを備えたUnitTest ++を使用していますが、抽象的なものはありません。フィクスチャは非常に文字通りであり、特定のテストグループに対して各テストで繰り返されるテストコードを統合する方法にすぎません。

@asgeo -リンクが動作するはず末尾のbracket.Thisを拾った...そのコメントを編集することはできません- goo.gl/dWp3k
Gishu

「立ち往生することは、設計にさらなる作業が必要な症状」の+1ですが、設計で立ち往生すると(私のように)どうなりますか?
ラーシャー

32

100%のテストカバレッジは必要ありません。実用的である。


2
100%のテストカバレッジがない場合、100%の自信はありません。
クリストファーマハン

60
テストカバレッジが100%であっても、100%の自信はありません。これはテスト101です。テストでは、コードに欠陥がないことを示すことはできません。それどころか、彼らはそれが欠陥を含んでいることを示すことができるだけです。
CesarGon

7
最も価値のあるものとして、最も情熱的なTDD支持者の1人であるBob Martinは、100%のカバレッジを推奨していません-blog.objectmentor.com/articles/2009/01/31/…。製造業(多くの点でソフトウェアとは異なります)で、99%の自信を得るために努力のほんの一部を費やすことができるため、100%の信頼を得ることはできません。
チャンス

また、(少なくとも、私たちが持っているツールをチェックしたとき)コードカバレッジレポートは、行が実行されたかどうかに関係しますが、値のカバレッジは含まれません。のようなa = x + y行があり、コード内のすべての行はテストで実行されましたが、テストはy = 0の場合にのみテストされたため、テストではバグa = x - yが見つかりませんでした。
ピートカーカム

@チャンス-ロバート・マーティンの本「Clean coder ...」を長い名前で読んだことがあります。それはその本で、テストで漸近的に100%カバーされるべきであり、100%に近いと述べた。そして、ブログのリンクはもう機能しません。
ダリウス.V

22

TDDはまだ私をかなり遅くしています

それは実際には間違っています。

TDDを使用しない場合、ほとんど機能するコードの作成に数週間を費やし、翌年には多くの(すべてではない)バグの「テスト」と修正に費やします。

TDDでは、実際に機能するコードを書くのに1年を費やします。その後、数週間にわたって最終的な統合テストを行います。

経過時間はおそらく同じになるでしょう。TDDソフトウェアは、品質が大幅に向上します。


6
では、なぜTDDが必要なのですか?「経過時間は同じです」

21
@Peter Long:コードの品質。「テスト」の年は、ほとんどが機能するがらくたソフトウェアをどうやって完成させるかです。
S.Lott

1
@ピーター、冗談を言わなければならない。TDDソリューションの品質ははるかに優れています。
マークトーマス

7
なぜTDDが必要なのですか?ケント・ベックは心の安らぎを大きなものとして挙げており、それは私にとって非常に魅力的です。私は、単体テストなしでコードに取り組んでいるとき、ものを壊すことを常に恐れています。

7
@Peter Long:「経過時間は同じです」...そしてその間のどの時点でも、作業コードを配信できます
フランクシェラー

20

または、ゲーム内の特定のエンティティとその作成と使用のすべての側面をカバーするために50回目のテストを終了した後、To Doリストを見て、コーディングする次のエンティティを確認し、執筆の思考に恐怖を覚えます実装するための別の50の同様のテスト。

これにより、TDDの「リファクタリング」手順をどれだけ実行しているかが気になります。

すべてのテストに合格したら、今度はコードをリファクタリングして重複を削除します。人々は通常これを覚えていますが、これはテストリファクタリングし、重複を取り除き、物事を単純化するときでもあることを忘れることがあります。

コードの再利用を可能にするために1つにマージする2つのエンティティがある場合、それらのテストもマージすることを検討してください。本当に必要なのは、コードの差分の増分をテストすることだけです。テストのメンテナンスを定期的に実行していないと、テストがすぐに扱いにくくなる可能性があります。

TDDに関するいくつかの哲学的ポイントは、役に立つかもしれません:

  • テストの作成経験が豊富であるにもかかわらず、テストの作成方法がわからない場合は、間違いなくコードのにおいです。あなたのコードはモジュール性に何らかの形で欠けているため、小さく単純なテストを書くのが難しくなります。
  • TDDを使用する場合、少しのコードをスパイクすることは完全に受け入れられます。目的のコードを記述して、どのように見えるかを理解し、コードを削除してテストから始めます。
  • 私は非常に厳しいTDDの練習を運動の一種として見ています。最初に始めるときは、必ず毎回必ず最初にテストを書き、次に進む前にテストに合格するための最も簡単なコードを書いてください。ただし、練習に慣れれば、これは必要ありません。私が書くすべての可能なコードパスのユニットテストはありませんが、経験を通じて、ユニットテストでテストする必要があるもの、機能テストまたは統合テストでカバーできるものを選択することができます。もしあなたがTDDを厳密な方法で1年間練習しているのなら、あなたもこの点に近いと思います。

編集:ユニットテストの哲学のトピックでは、これはあなたが読むのに興味深いかもしれないと思う:テスティウスの道

そして、必ずしも実用的ではないにしても、より実用的なポイントは次のとおりです。

  • 開発言語としてC ++に言及しています。JUnitやMockitoなどの優れたライブラリを使用して、JavaでTDDを広範囲に実践しました。ただし、C ++のTDDは、ライブラリ(特に、モックフレームワーク)が利用できないため、非常にイライラすることがわかりました。この点は現在の状況ではあまり役に立ちませんが、TDDを完全に廃止する前に考慮に入れてください。

4
テストのリファクタリングは危険です。誰もこれについて話していないようですが、そうです。ユニットテストをテストするためのユニットテストは確かにありません。重複を減らすためにリファクタリングすると、通常、複雑さが増します(コードがより一般的になるため)。これは、テストにバグがある可能性が高いことを意味します。
スコットホイットロック

2
リファクタリングテストが危険であることには同意しません。すべてが合格した場合にのみリファクタリングを行うので、リファクタリングを実行してもすべてがまだ緑色の場合は、問題ありません。あなたがあなたのテストのためにテストを書く必要があると思っているなら、私はそれがあなたがより簡単なテストを書く必要があることの指標だと感じます。
ジャスティン

1
C ++は単体テストが困難です(この言語はモックを簡単にするものを簡単に実行できません)。「関数」(引数のみで動作し、結果が戻り値/パラメータとして出力される)である関数は、「手続き」(voidを返す、引数なし)よりもテストがはるかに簡単であることに気付きました。実際、C ++コードよりも、巧妙に作成されたモジュラーCコードを単体テストする方が簡単であることがわかりました。Cで書く必要はありませんが、モジュラーCの例に従うことができます。それは完全に狂気に聞こえますが、私はすべてがグローバルであり、それが非常に簡単である「悪いC」にユニットテストを置きました-すべての状態が常に利用可能です!
アノン

2
これは非常に真実だと思います。私は多くのRedGreenRedGreenRedGreen(または、より頻繁に、RedRedRedGreenGreenGreen)をしますが、私はめったにリファクタリングしません。私のテストは確かにリファクタリングされたことはありません。コーディングしないとさらに時間を浪費するといつも感じていたからです。しかし、私が今直面している問題の原因がどのようになっているのかがわかります。リファクタリングと統合を真剣に検討する時間です。
ナイロウ

1
Google C ++モックフレームワーク(google C ++テストfwと統合)-非常に強力なモックライブラリ-柔軟で機能豊富-他のモックフレームワークと非常に匹敵します。
-ratkok

9

非常に興味深い質問です。

注意すべき重要なことは、C ++は簡単にテストできないことであり、ゲームは一般にTDDの非常に悪い候補でもあります。OpenGL / DirectXがドライバーXで三角形の赤を描き、ドライバーYで黄色の三角形を簡単に描くかどうかはテストできません。バンプマップの法線ベクトルがシェーダー変換後に反転されない場合。また、異なる精度のドライバーバージョンでクリッピングの問題をテストすることもできません。誤った呼び出しによる未定義の描画動作は、正確なコードレビューとSDKを使用してのみテストすることもできます。音も悪い候補です。ゲームにとって非常に重要なマルチスレッドは、単体テストにはほとんど役に立ちません。大変です。

基本的に、ゲームは多くのGUI、サウンド、スレッドです。GUIは、WM_を送信できる標準コンポーネントであっても、単体テストが困難です。

テストできるのは、モデルの読み込みクラス、テクスチャの読み込みクラス、マトリックスライブラリなどです。これは多くのコードではなく、多くの場合、あまり再利用できません(最初のプロジェクトのみの場合)。また、独自の形式にパックされているため、改造ツールなどをリリースしない限り、サードパーティの入力が大きく異なる可能性はほとんどありません。

繰り返しますが、私はTDDの第一人者でも伝道者でもないので、すべてを塩の粒で取ります。

おそらく、主要なコアコンポーネント(たとえば、マトリックスライブラリ、イメージライブラリ)のテストを作成します。abort()すべての関数に予期しない入力の束を追加します。そして最も重要なことは、簡単に壊れない耐性/復元力のあるコードに集中することです。

1つのエラーについては、C ++、RAII、および優れた設計の巧妙な使用が、それらを防ぐために大いに役立ちます。

基本的に、ゲームをリリースしたい場合、基本をカバーするためにやることがたくさんあります。TDDが役立つかどうかはわかりません。


3
+1私はTDDの概念が本当に好きで、どこでもそれを使用しますが、TDDの支持者が不思議なことに黙っているという非常に重要なポイントを提起します。あなたが指摘したように、意味のある単体テストを書くのは不可能ではないにしても非常に難しいプログラミングの種類がたくさんあります。理にかなっている場合はTDDを使用しますが、一部のタイプのコードは他の方法でより適切に開発およびテストされます。
マークヒース

@Mark:うん、誰も自動化されたテストスイートを持っているので、統合テストを気にしているように見えません。
gbjbaanb

これに同意します。開発者ツールキットの別のツールであるTDDが何であるかではなく、すべてに対する答えとして独断的にTDDを規定していない実用的な答えをありがとう。
jb

6

私は他の答えにも同意しますが、非常に重要な点を追加したいと思います:リファクタリングコスト!!

よく書かれた単体テストを使用すると、コードを安全に書き直すことができます。まず、よく書かれた単体テストは、コードの意図を示す優れたドキュメントを提供します。第二に、リファクタリングの不幸な副作用は、既存のテストスイートで捕捉されます。したがって、古いコードの仮定が新しいコードにも当てはまることを保証しました。


4

すべての生産性と動機を損なうことなく、他の人々はどのようにTDDを生き延びますか?

これは私の経験とはまったく異なります。あなたは驚くほど頭が良く、バグのないコードを書く(例えば、エラーが1つずれている)か、コードがプログラムの動作を妨げるバグを持っていることに気づかないので、実際には終了していません。

TDDは、あなた(そして私も!)が間違いを犯したことを謙虚に知ることです。

私にとって、ユニットテストを書く時間は、TDDを最初から使用して行われるプロジェクトのデバッグ時間を短縮することで節約できます。

間違いを犯さなければ、TDDは私にとって重要ではないでしょう。


さて、TDDコードにもバグがあります;)
コーダー

それは本当だ!しかし、TDDが適切に行われている場合、それらは異なるタイプのバグになる傾向があります。私は、コードを完成させるために100%のバグがなければならないと言っているのは正しくないと思います。単体テストで定義された動作からの逸脱としてバグを定義した場合、バグはないと思います:)
トム

3

私はほんの少数の発言をしました:

  1. すべてをテストしようとしているようです。特定のコード/メソッドの高リスクおよびエッジケースだけを行うべきではありません。ここでは80/20のルールが適用されると確信しています。80%が、カバーされていないコードまたはケースの最後の20%のテストの作成に費やしています。

  2. 優先順位を付けます。アジャイルなソフトウェア開発に入り、1か月以内にリリースするために本当に本当に必要なことのリストを作成します。次に、そのようにリリースします。これにより、機能の優先順位について考えるようになります。ええ、あなたのキャラクターがバク転をすることができればクールですが、ビジネス価値はありますか?

TDDは優れていますが、100%のテストカバレッジを目指していない場合のみであり、実際のビジネス価値(つまり、機能、ゲームに何かを追加するもの)の生成を妨げない場合はそうではありません。


1

はい、テストとコードの作成はコードの作成よりも時間がかかる場合がありますが、コードと関連する単体テスト(TDDを使用)の作成は、コードを作成してデバッグするよりもはるかに予測可能です。

TDDを使用すると、デバッグはほとんどなくなります。これにより、すべての開発プロセスがはるかに予測可能になり、最終的にはより高速になります。

継続的なリファクタリング-包括的な単体テストスイートなしで深刻なリファクタリングを行うことは不可能です。そのユニットテストベースのセーフティネットを構築する最も効率的な方法は、TDD中です。適切にリファクタリングされたコードは、コードを保守するデザイナー/チームの全体的な生産性を大幅に向上させます。


0

ゲームの範囲を狭めて、誰かがプレイできる場所、またはリリースする場所を取得することを検討してください。ゲームをリリースするのにあまり長く待たずにテスト基準を維持することは、やる気を維持するための妥協点になります。ユーザーからのフィードバックは、長期的に利益をもたらす可能性があり、テストにより、追加や変更に慣れることができます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.