私はTDD、特に開発部分に頭を抱えようとしています。私はいくつかの本を見てきましたが、私が見つけたものは主にテストの部分に取り組みます-NUnitの歴史、テストがなぜ良いのか、Red/Green/Refactor、そして文字列計算機の作成方法。
良いことですが、それはTDDではなく「単なる」単体テストです。具体的には、TDDがテストを開始するためにデザインが必要な場合に、TDDが優れたデザインを取得するのにどのように役立つかがわかりません。
説明のために、次の3つの要件を想像してください。
この時点で、多くの本は帽子から魔法のウサギを引き抜いて「Testing the ProductService」に飛び込んでいますが、最初にProductServiceがあるという結論に至った経緯については説明していません。それが私が理解しようとしているTDDの「開発」の部分です。
既存の設計が必要ですが、エンティティサービス以外のもの(つまり、製品があるため、ProductServiceがあるはずです)が見つかりません(たとえば、2番目の要件では、ユーザーですが、機能をどこにリマインドしますか?検索はProductServiceまたは別のSearchServiceの機能ですか?どれを選択すればよいかをどのようにして知ることができますか?)
[〜#〜] solid [〜#〜] によると、UserServiceが必要になりますが、TDDを使用しないシステムを設計すると、結局は単一メソッドサービスの束になってしまう可能性があります。そもそもTDDは自分のデザインを最初に発見させることを目的としていませんか?
私は.net開発者ですが、Javaリソースも機能します。実際の基幹業務アプリケーションを扱う実際のサンプルアプリケーションや本はないようです。誰かがTDDを使用してデザインを作成するプロセスを示す明確な例を提供できますか?
TDDのアイデアは、テストから始めて、そこから作業することです。したがって、「カタログには製品のリストが必要です」の例をとると、「カタログ内の製品を確認する」というテストがあると見なすことができるため、これが最初のテストになります。今、何がカタログを保持していますか?何が製品を保持していますか?これらは次の部分であり、アイデアは、最初のテストに合格することから生まれるProductServiceのようなものを組み立てることです。
TDDの考え方は、テストから始めて、そのテストを最初のポイントとしてパスさせるコードを書くことです。ユニットテストはこれの一部ですが、コードがないため、この時点で死角が発生しないように、テストから開始してコードを作成することで形成される全体像を見ていません。
テスト駆動開発チュートリアル スライド20〜22が重要です。その目的は、結果として機能が何をすべきかを知り、そのためのテストを記述して、ソリューションを構築することです。必要なものに応じて、設計部分は異なります。重要なポイントは、プロジェクトの後半に導入するのではなく、最初からTDDを使用することです。最初にテストを開始する場合、これは役立つ可能性があり、ある意味で注目に値する可能性があります。後でテストを追加しようとすると、延期または遅延する可能性があります。後のスライドも役立つかもしれません。
TDDの主な利点は、テストから始めることで、最初は設計に縛られないことです。したがって、アイデアはテストを構築し、それらのテストを開発方法論として渡すコードを作成することです。 A Big Design Up Front は問題を引き起こす可能性があります。これにより、物事を所定の位置に固定するというアイデアが得られ、最終的に構築中のシステムの機敏性が低下します。
Robert Harveyがコメントにこれを追加しましたが、答えで述べる価値があります。
残念ながら、これはTDDに関する一般的な誤解だと思います:単体テストを作成してそれらをパスさせるだけではソフトウェアアーキテクチャを拡張することはできません。ユニットの作成テストはデザインに影響しますが、デザインには影響しません作成。あなたはそれをしなければなりません。
価値があることについて、TDDは、TDDを行わないよりも、(より迅速に)最高の設計を実現するのに役立ちます。私はおそらくそれの有無に関わらず最高のデザインになるでしょう。しかし、私がそれをじっくり考えて、コードをいくつか試してみた時間は、代わりにテストの作成に費やされていました。そして、それはより少ない時間です。私のために。みんなのためではありません。そして、同じ時間がかかったとしても、一連のテストが残っているので、リファクタリングがより安全になり、コードの改善につながります。
どうやってやるの?
まず、すべてのクラスをクライアントコードへのサービスとして考えることを奨励します。より良いコードは、コード自体がどうあるべきかを心配するのではなく、呼び出し側のコードがAPIをどのように使用したいかを考えることから生まれます。
第二に、私はそれを考えている間、私は1つの方法にあまりにも多くの循環的複雑度を書くのを止めます。メソッドを介した各追加パスは、実行する必要があるテストの数を2倍にする傾向があります。純粋な遅延は、あまりにも多くのロジックを追加した後、1つの条件を追加するために16のテストを作成する必要がある場合、それを別のメソッド/クラスに引き出して個別にテストする時が来たことを示します。
とても簡単です。魔法のデザインツールではありません。
TDDに頭を抱えようとしています...例として、次の3つの要件を想像してください。
- カタログには製品のリストが必要です
- カタログは、ユーザーがどの製品を表示したかを記憶する必要があります
これらの要件は、人間の言葉で言い換える必要があります。ユーザーが以前に閲覧した製品を知りたいのですか?ユーザー?セールスマン?
- ユーザーは製品を検索できる必要があります
どうやって?名前で?ブランド別?テスト駆動開発の最初のステップは、テストを定義することです。次に例を示します。
browse to http://ourcompany.com
enter "cookie" in the product search box
page should show "chocolate-chip cookies" and "oatmeal cookies"
>
この時点で、多くの本は帽子から魔法のウサギを引き抜いて「Testing the ProductService」に飛び込んでいますが、最初にProductServiceがあるという結論に至った経緯については説明していません。
これらが唯一の要件である場合、私は確かにProductServiceを作成するために跳躍しません。静的な製品リストを含む非常に単純なWebページを作成する場合があります。これは、製品を追加および削除するための要件に到達するまで完全に機能します。その時点で、リレーショナルデータベースとORMを使用して、単一のテーブルにマップされたProductクラスを作成するのが最も簡単だと思うかもしれません。まだProductServiceはありません。 ProductServiceのようなクラスは、必要なときに作成されます。同じクエリまたは更新を実行する必要がある複数のWebリクエストがある場合があります。次に、コードの重複を防ぐためにProductServiceクラスが作成されます。
要約すると、TDDは記述されるコードを駆動します。設計は実装の選択を行うときに発生し、コードをクラスにリファクタリングして重複を排除し、依存関係を制御します。コードを追加するとき、コードをSOLIDに保つために新しいクラスを作成する必要があります。ただし、ProductクラスとProductServiceクラスが必要かどうかを事前に判断する必要はありません。 Productクラスだけでも、人生は完璧であることがわかるでしょう。
他の人はそう思わないかもしれませんが、私にとって新しい方法論の多くは、開発者が習慣や個人的なプライドから出てきた古い方法論の大部分を実行するという仮定に依存しています。作業はクリーンな言語でカプセル化されるか、やや乱雑な言語のクリーンな部分でカプセル化されるため、すべてのテストビジネスを実行できます。
私が過去にこれに遭遇したいくつかの例:
一連の仕様作業請負業者を取り、彼らのチームがアジャイルでテストファーストであることを伝えます。彼らはしばしば、仕様に合わせて作業する以外に癖がなく、プロジェクトを完了するのに十分な期間続く限り、作業の品質に関心を持ちません。
最初に何か新しいテストを試してみてください。さまざまなアプローチやインターフェースが不適切であることがわかったら、テストのリッピングに多くの時間を費やしてください。
低レベルの何かをコーディングし、カバレッジの欠如のために平手打ちをかけるか、関連付けられている基本的な動作を模擬できないため、あまり価値のない多くのテストを記述します。
TDDを実行していてTDDがうまく機能しているなら、それは良いことですが、そこには多くのこと(全体のジョブ、またはプロジェクトの段階)があり、それだけでは価値がありません。
あなたの例は、まだデザインに慣れていないように聞こえるので、アーキテクチャについて話し合う必要があるか、プロトタイプを作成しています。私の意見では、最初にそのいくつかを経験する必要があります。
TDDは、システムのdetailed設計(つまり、APIとオブジェクトモデル)への非常に貴重なアプローチであると確信しています。ただし、TDDの使用を開始するプロジェクトのポイントに到達するには、既に何らかの方法でモデル化された設計の全体像を持ち、何らかの形ですでにモデル化されたアーキテクチャーの全体像を持っている必要があります。 @ user414076は、Robert Martinがデザインのアイデアを念頭に置いているが、それとは結婚していないと言い換えています。丁度。結論-TDDは現在進行中の唯一の設計活動ではなく、設計の詳細が具体化される方法です。 TDDの前に他の設計活動を行い、全体的な設計がどのように作成および進化するかを扱う全体的なアプローチ(アジャイルなど)に合わせる必要があります。
FYI-私が具体的で現実的な例を与えるトピックについて私が推薦する2冊の本:
Growing Object-Oriented Software、Guided by Tests -完全なプロジェクトの例を説明し、提供します。これはデザインに関する本であり、テストではありません。テストは、設計作業中に予想される動作を指定する手段として使用されます。
テスト駆動開発の実践ガイド -小さいながら、完全なアプリの開発をゆっくりと段階的に説明します。
機能には多くの設計があり、TDDはどれが最適であるかを完全には通知しません。テストがモジュール化されたコードの構築に役立つ場合でも、テストの要件に適合し、実際の運用ではないモジュールを構築することにもつながります。ですから、あなたは自分がどこに向かっているのか、そして全体像にどのように収まるのかを理解する必要があります。言い換えると、機能要件と非機能要件があります。最後の要件を忘れないでください。
設計に関しては、Robert C. Martinの本(アジャイル開発)だけでなく、Martin Fowlerのエンタープライズアプリケーションアーキテクチャのパターンとドメインドライバーの設計も参照します。後者は特に、要件からエンティティと関係を抽出する際に非常に体系的です。
次に、それらのエンティティーを管理する方法について利用可能なオプションの良い感じが得られたら、TDDアプローチを提供できます。
TTDは、成功ではなくテストの失敗によって設計の発見を促進します。したがって、未知がテストされ、繰り返し再テストできるため、最終的に単体テストの完全なハーネスにつながります。コードが作成/リリースされた後の改造。
たとえば、要件は、入力がいくつかの異なる形式である可能性があることである場合がありますが、まだすべてがわかっているわけではありません。 TDDを使用して、まずany入力形式で適切な出力が提供されることを確認するテストを記述します。明らかにこのテストは失敗するので、既知のフォーマットを処理して再テストするコードを記述します。要件の収集を通じて未知の形式が公開されるため、新しいテストが記述されますbeforeコードが記述されますが、これらも失敗するはずです。次に、新しいコードを記述して新しい形式をサポートし、allテストを再実行して、回帰の可能性を減らします。
ユニットの障害を「壊れた」コードではなく「未完成の」コードと考えることも役立ちます。 TDDは未完了のユニット(予期しない障害)を許容しますが、壊れたユニット(予期しない障害)の発生を減らします。
質問ではそれが述べられています:
...多くの本は帽子から魔法のウサギを引き抜いて「Testing the ProductService」に飛び込んでいますが、彼らはそもそもProductServiceがあるという結論に至った方法を説明していません。
彼らは、この製品をどのようにテストするかを考えて、その結論に達しました。 「これはどんな製品ですか?」 「そうですね、サービスを作成することができました」。 「よし、そのようなサービスのテストを書いてみよう」
そもそもTDDは、自分のデザインを最初に発見させることを目的としていませんか?
番号。
最初に設計していないものをどのようにテストできますか?
説明のために、次の3つの要件を想像してください。
- カタログには製品のリストが必要です
- カタログは、ユーザーがどの製品を表示したかを記憶する必要があります
- ユーザーは製品を検索できる必要があります
これらは要件ではなく、データの定義です。私はあなたのソフトウェアのビジネスが何であるかわかりませんが、アナリストがそのように話すことはありそうもありません。
システムの不変条件を知る必要があります。
要件は次のようになります。
したがって、これが唯一の要件である場合、次のようなクラスがある可能性があります。
public class Product {
private int quantity;
public Product(int initialQuantity) {
this.quantity = initialQuantity;
}
public void order(int quantity) {
// To be implemented.
}
}
次にTDDを使用して、order()メソッドを実装する前にテストケースを記述します。
public void ProductTest() {
public void testCorrectOrder() {
Product p = new Product(10);
p.order(3);
p.order(4);
}
@Expect(ProductOutOfStockException)
public void testIncorrectOrder() {
Product p = new Product(10);
p.order(7);
p.order(4);
}
}
したがって、2番目のテストは失敗し、order()メソッドを好きな方法で実装できます。
あなたは全く正しいですTDDは与えられたデザインの良いimplementationをもたらします。それはあなたの設計プロセスを助けません。