web-dev-qa-db-ja.com

TDDで「実際の」コードを書くのはいつですか?

私が読んでトレーニングビデオで見たすべての例には、単純な例があります。しかし、環境に優しくなった後に「実際の」コードをどのように実行すればよいのかわかりません。これは「リファクタリング」の部分ですか?

複雑なメソッドを持つかなり複雑なオブジェクトがあり、合格するためのテストと最低限の要件を記述した場合(最初に失敗した後、赤)。いつ戻って実際のコードを書くのですか?そして、再テストする前にどれだけの実際のコードを書けばよいのでしょうか最後のほうが直感的だと思います。

編集:回答したすべての人に感謝します。あなたの答えはすべて私を大きく助けてくれました。私が求めていた、または混乱していたことについては異なる考えがあるようですが、おそらくあるかもしれませんが、私が求めていたのは、学校を建設するためのアプリケーションがあると言うことでした。

私のデザインには、最初に使用したいアーキテクチャ、ユーザーストーリーなどが含まれています。ここから、それらのユーザーストーリーを取り上げ、ユーザーストーリーをテストするためのテストを作成します。ユーザーによると、学校に登録して登録料を支払う人がいます。だから、それを失敗させる方法を考えます。そうすることで、失敗するクラスX(たぶんStudent)のテストクラスを設計します。次に、「学生」というクラスを作成します。たぶん「学校」は知りません。

しかし、いずれにせよ、TDDesignは、ストーリーをじっくりと考えることを余儀なくさせています。テストが失敗する理由はわかりますが、これは成功させることができると仮定しています。

私はこれを再帰について考えることに似ています。再帰は難しい概念ではありません。頭の中で実際に追跡するのは難しいかもしれませんが、実際には、再帰が「壊れる」とき、いつ停止するか(もちろん、私の意見です)を知るのが最も難しいのです。最初に再帰。これは不完全な類推にすぎず、再帰的な各反復が「パス」であると想定しています。もう一度、ただの意見です。

実装では、学校は見にくいです。数値および銀行の元帳は、単純な算術を使用できるという意味で「簡単」です。私はa + bを表示して0を返す、などができます。人々のシステムの場合、それを実装する方法についてもっと考えなければなりません。私は失敗、合格、リファクタリングの概念を持っています(主に研究とこの質問のため)。

私の知らないことは、私の意見では、経験の欠如に基づいています。新しい学生のサインアップに失敗する方法がわかりません。誰かが姓を入力してデータベースに保存されることに失敗する方法がわかりません。単純な数学でa + 1を作成する方法は知っていますが、人のようなエンティティでは、誰かが名前を入力したときにデータベースの一意のIDまたは何か他のものが返されるかどうかをテストするだけなのかわかりませんデータベースまたはその両方、あるいはその両方。

または、おそらくこれは私がまだ混乱していることを示しています。

150
johnny

複雑なメソッドを持つかなり複雑なオブジェクトがあり、合格するためのテストと最低限の要件を記述した場合(最初に失敗した後、赤)。いつ戻って実際のコードを書くのですか?そして、再テストする前にどれだけの実際のコードを書けばよいのでしょうか最後のほうが直感的だと思います。

「戻って」「本物のコード」を書くのではありません。それはすべて本物のコードです。新しいテストに合格するために、コードに戻ってforcesあなたにchangeコードを追加する別のテストを追加します。

再テストする前にどのくらいのコードを書きますか?無し。 zerosのテストを失敗せずにforcesコードを記述して、さらにコードを記述します。

パターンに気づきましたか?

それが役立つことを願って、(別の)簡単な例を見てみましょう。

Assert.Equal("1", FizzBuzz(1));

イージーピージー。

public String FizzBuzz(int n) {
    return 1.ToString();
}

実際のコードとは違うでしょう?変更を強制するテストを追加してみましょう。

Assert.Equal("2", FizzBuzz(2));

if n == 1、ただし、正解にスキップします。

public String FizzBuzz(int n) {
    return n.ToString();
}

涼しい。これは、FizzBu​​zz以外のすべての番号で機能します。量産コードを強制的に変更する次の入力は何ですか?

Assert.Equal("Fizz", FizzBuzz(3));

public String FizzBuzz(int n) {
    if (n == 3)
        return "Fizz";
    return n.ToString();
}

そしてまた。まだ通らないテストを書いてください。

Assert.Equal("Fizz", FizzBuzz(6));

public String FizzBuzz(int n) {
    if (n % 3 == 0)
        return "Fizz";
    return n.ToString();
}

そして、3の倍数すべてをカバーしました(5の倍数ではありません。これに注意して戻ってきます)。

「バズ」のテストはまだ書いていないので、書きましょう。

Assert.Equal("Buzz", FizzBuzz(5));

public String FizzBuzz(int n) {
    if (n % 3 == 0)
        return "Fizz";
    if (n == 5)
        return "Buzz"
    return n.ToString();
}

また、処理する必要がある別のケースがあることもわかっています。

Assert.Equal("Buzz", FizzBuzz(10));

public String FizzBuzz(int n) {
    if (n % 3 == 0)
        return "Fizz";
    if (n % 5 == 0)
        return "Buzz"
    return n.ToString();
}

また、3の倍数ではない5の倍数すべてを処理できるようになりました。

この時点まで、リファクタリングのステップは無視してきましたが、重複が見られます。それをクリーンアップしましょう。

private bool isDivisibleBy(int divisor, int input) {
    return (input % divisor == 0);
}

public String FizzBuzz(int n) {
    if (isDivisibleBy(3, n))
        return "Fizz";
    if (isDivisibleBy(5, n))
        return "Buzz"
    return n.ToString();
}

涼しい。これで重複を取り除き、名前の付いた関数を作成しました。コードを変更するように強制する次のテストは何ですか?まあ、数が3と5の両方で割り切れるケースは避けてきました。今すぐ書きましょう。

Assert.Equal("FizzBuzz", FizzBuzz(15));

public String FizzBuzz(int n) {
    if (isDivisibleBy(3, n) && isDivisibleBy(5, n))
        return "FizzBuzz";
    if (isDivisibleBy(3, n))
        return "Fizz";
    if (isDivisibleBy(5, n))
        return "Buzz"
    return n.ToString();
}

テストはパスしましたが、重複があります。オプションはありますが、「ローカル変数の抽出」を数回適用して、書き換えるのではなくリファクタリングします。

public String FizzBuzz(int n) {

    var isDivisibleBy3 = isDivisibleBy(3, n);
    var isDivisibleBy5 = isDivisibleBy(5, n);

    if ( isDivisibleBy3 && isDivisibleBy5 )
        return "FizzBuzz";
    if ( isDivisibleBy3 )
        return "Fizz";
    if ( isDivisibleBy5 )
        return "Buzz"
    return n.ToString();
}

そして、すべての合理的な入力をカバーしましたが、不合理入力はどうですか? 0または負の値を渡すとどうなりますか?それらのテストケースを記述します。

public String FizzBuzz(int n) {

    if (n < 1)
        throw new InvalidArgException("n must be >= 1);

    var isDivisibleBy3 = isDivisibleBy(3, n);
    var isDivisibleBy5 = isDivisibleBy(5, n);

    if ( isDivisibleBy3 && isDivisibleBy5 )
        return "FizzBuzz";
    if ( isDivisibleBy3 )
        return "Fizz";
    if ( isDivisibleBy5 )
        return "Buzz"
    return n.ToString();
}

これはまだ「実際のコード」のように見え始めていますか?さらに重要なのは、どの時点で「非現実的なコード」でなくなり、「現実の」コードに移行したのでしょうか。それは熟考するためのものです...

そのため、各ステップで合格しないことがわかっているテストを探すだけでこれを行うことができましたが、多くの練習をしました。私が仕事をしているとき、物事はこれほど単純なものではなく、どのテストが変更を強制するかを常に知っているとは限りません。時々私はテストを書いて、それがすでに合格しているのを見て驚きます!開始する前に、「テストリスト」を作成する習慣を身に付けることを強くお勧めします。このテストリストには、考えられるすべての「興味深い」入力が含まれている必要があります。すべてを使用するわけではなく、ケースを追加する可能性がありますが、このリストはロードマップとして機能します。 FizzBu​​zzのテストリストは次のようになります。

  • ゼロ
  • 1
  • 6(3の自明ではない倍数)
  • 9(3乗)
  • 10(5の自明でない倍数)
  • 15(3と5の倍数)
  • 30(3および5の自明でない倍数)
241
RubberDuck

「実際の」コードは、テストに合格するために記述するコードです。 本当に。とても簡単です。

人々がテストを環境に優しいものにするための最低限のことを書くことについて話すとき、それはあなたの本当のコードが YAGNI原則 に従うべきであることを単に意味します。

リファクタリングステップのアイデアは、要件を満たしていると満足したら、作成した内容をクリーンアップすることです。

作成するテストが実際に製品の要件を網羅している限り、テストに合格するとコードが完成します。考えてみてください。すべてのビジネス要件にテストがあり、それらすべてのテストが環境に優しい場合、他に何を書く必要がありますか? (さて、実際には、完全なテストカバレッジを持つ傾向はありませんが、理論は確かです。)

45
GenericJon

簡単に言えば、「実際のコード」はテストに合格するコードです。実際のコード以外でテストに合格できる場合は、テストを追加してください!

TDDに関するチュートリアルの多くは単純化していることに同意します。それは彼らに反する。たとえば、3 + 8を計算するメソッドの単純すぎるテストは、本当に3 + 8を計算して結果を比較する以外に選択肢はありません。これにより、コード全体を複製しているように見え、そのテストは無意味でエラーが発生しやすい余分な作業です。

テストが得意な場合は、アプリケーションの構造とコードの記述方法がわかります。実用的で役立つテストを思い付くのに問題がある場合は、おそらく設計を少し考え直す必要があります。適切に設計されたシステムはテストが簡単です。つまり、実用的なテストは簡単に考え、実装できます。

最初にテストを作成し、それらが失敗するのを監視してから、テストを成功させるコードを作成します。これは、すべてのコードに対応するテストがあることを確認するための規律です。私はコーディングの際にそのルールを怠りません。事実の後でテストを書くことがよくあります。しかし、最初にテストを行うと、正直に保つことができます。ある程度の経験があると、最初にテストを作成していない場合でも、隅に自分をコーディングしているときに気付くようになります。

14
Carl Raymond

TDDに関するいくつかの例が誤解を招くことがあります。他の人が以前指摘したように、テストに合格させるために作成するコードは実際のコードです。

しかし、実際のコードが魔法のように見えるとは思わないでください-それは間違っています。達成したいことをよりよく理解してから、最も簡単なケースとコーナーケースから始めて、それに応じてテストを選択する必要があります。

たとえば、レクサーを作成する必要がある場合は、空の文字列から始め、次に空白の数、次に数字、次に空白で囲まれた数字、次に間違った数字などを使用します。これらの小さな変換により、適切なアルゴリズムですが、実際のコードを実行するために、最も簡単なケースから非常に複雑なケースにジャンプしないでください。

ボブ・マーティンはそれを完全に説明します ここ

6
Victor Cejudo

疲れて家に帰りたいとき、リファクタリングの部分は片付けられます。

機能を追加しようとしているときは、次のテストの前に変更するのがリファクタリング部分です。コードをリファクタリングして、新しい機能のためのスペースを作ります。 knowのときにこれを行います==その新機能はどうなるか。想像しているだけではありません。

これは、GreetImplGreetWorldに名前変更して(テストを追加した後)GreetMomクラスを作成して「Hi Mom」を印刷する機能を追加するのと同じくらい簡単です。

5
candied_orange

あなたはずっとReal Codeを書いています。

各ステップで、あなたはあなたのコードがあなたのコードの将来の呼び出し元のために満たす条件を満たすあなたがコードを書いている(あなたかもしれないし、そうでないかもしれない...)。

便利な(real)コードを書いていないと思うでしょう。

Code-Refactoring は、外部の動作を変更せずに、既存のコンピューターコードを再構築するプロセスです。

これが意味することは、あなたがコードを変更しているにもかかわらず、コードが満足する条件は変更されないままになっているということです。そして、チェック(tests)あなたが実装したコードは、あなたの変更が何か変更したかどうかを確認するためにすでにそこにあることを確認します。ですから、あなたがずっと書いたコードは、別の方法でそこにあります。

あなたがそれが本当のコードではないと思うかもしれないもう一つの理由は、あなたがエンドプログラムをすでにあなたが予見できるような例をあなたがしているということです。これは、プログラミング中のdomainについての知識があることを示しているため、非常に優れています。
しかし、多くの場合、プログラマーはdomainにいますnewunknown 彼らへ。彼らは最終結果がどうなるかを知らず、TDDはプログラムを段階的に記述して(= /// =)知識を文書化するテクニックですこのシステムがどのように機能するか、およびコードがそのように機能することを確認する方法について。

TDDでThe Book(*)を読んだとき、私にとって目立った最も重要な機能はTODOリストでした。 TDDは、開発者が一度に1つのことに集中できるようにするための手法でもあることがわかりました。だから、これはあなたの質問に対する回答でもありますどのくらいの本当のコードを書くか?一度に1つのことに集中するのに十分なコードと言えるでしょう。

(*)Kent Beckによる「テスト駆動開発:例による」

1

しかし、実際のコードはTDDフェーズのリファクタリング段階で表示されます。つまり最終リリースの一部となるコード。

変更を加えるたびにテストを実行する必要があります。

TDDライフサイクルのモットーは次のようになります。 RED GREEN REFACTOR

[〜#〜]赤[〜#〜]:テストを記述します

[〜#〜] green [〜#〜]:できるだけ迅速にテストに合格する機能コードを正直に試みます:重複したコード、あいまいに最上位の名前付き変数ハックなど.

[〜#〜] refactor [〜#〜]:コードをクリーンアップし、変数に適切な名前を付けます。 [〜#〜] dry [〜#〜] コードをアップします。

1
graeme

TDDで「実際の」コードを書くのはいつですか?

redフェーズでは、writeコードを記述します。

refactoringフェーズでは、主な目的はdeleteコードです。

redフェーズでは、テスト合格をできるだけ速くするために何でも行いますおよび費用。優れたコーディングプラクティスやデザインパターンについて聞いたことがあるものを完全に無視します。テストをグリーンにすることはすべて問題です。

refactoringフェーズでは、作成した混乱をクリーンアップします。次に、最初に、行った変更が Transformation Priority list の最上位の種類であるかどうかを確認します。コードの重複がある場合は、デザインパターンを適用することで削除できる可能性が高くなります。

最後に、識別子の名前を変更して読みやすくし、マジックナンバーやリテラル文字列を定数に抽出します。


赤のリファクタリングではなく、赤緑のリファクタリングです。 –ロブ・キニヨン

これを指摘してくれてありがとう。

つまり、greenフェーズであり、実際のコードを書きます

redフェーズでは、実行可能仕様...

1
Timothy Truckle

テストを失敗させるコードを書いているわけではありません。

成功するコードを定義するためのテストを作成します。合格するコードをまだ作成していないため、最初はすべて失敗するはずです。

最初に失敗するテストを書くことの要点は、2つのことを行うことです。

  1. すべてのケースをカバー-すべての名目上のケース、すべてのEdgeケースなど.
  2. テストを検証します。それらが通過するのを見ただけの場合、どのようにしたら、障害が発生したときに確実に障害を報告することができますか?

Red-green-refactorの背後にあるポイントは、正しいテストを最初に作成すると、テストに合格するために作成したコードが正しいことを知る自信が得られ、テストがすぐに通知するという確信を持ってリファクタリングできることです何かが壊れているので、すぐに戻って修正できます。

私自身の経験(C#/。NET)では、純粋なテストファーストは達成不可能な理想のビットです。これは、まだ存在しないメソッドの呼び出しをコンパイルできないためです。したがって、「最初にテストする」とは、実際にインターフェイスをコーディングして実装をスタブ化し、次にスタブが適切に具体化されるまでスタブ(最初は失敗します)に対するテストを作成することです。私は今まで「失敗したコード」を書くのではなく、単にスタブからビルドするだけです。

1
Zenilogix

単体テストと統合テストの間で混乱するかもしれません。受け入れテストもあると思いますが、それはあなたのプロセスに依存します。

小さな「ユニット」をすべてテストしたら、それらをすべて組み立て、または「統合」してテストします。これは通常、プログラム全体またはライブラリ全体です。

私が作成したコードでは、データを読み取ってライブラリーにフィードするさまざまなテストプログラムを使用してライブラリーをテストし、結果を確認しています。次に、スレッドでそれを行います。次に、スレッドとfork()を途中で使用します。次に、それを実行し、2秒後に-9を強制終了します。次に、起動して、回復モードを確認します。ファズします。いろいろな方法で拷問します。

これらはすべてALSOのテストでもありますが、結果をかなり赤/緑で表示できません。成功するか、数千行のエラーコードを調べて理由を調べます。

ここで「実際のコード」をテストします。

そして、私はこれについて考えましたが、ユニットテストの作成がいつ完了することになっているのか、あなたにはわかりません。テストで指定したすべてのことをテストが実行するとき、ユニットテストの記述は完了です。すべてのエラー処理とEdgeケースの間でそれを見失うことがあります。そのため、仕様を直接通過するハッピーパステストのニーステストグループを作成することができます。

0
Zan Lynx