3

私は長い間ここに潜んで助けを求めてきましたが、現在の問題に対する答えが見つかりません。

バック情報

私はいくつかの単体テストを書いています(イェイ!)。インターフェイスを実装する 40 個のオブジェクトがあります。そのインターフェイスの 1 つの関数は、1 つの Rectangle と 1 つの Rectangle の配列の 2 つのパラメーターを受け入れます。

public function foobar(foo:Rectangle, bar:Array/*Rectangle*/):void;

これらの 40 個のオブジェクトのそれぞれについてテストを書きたいと思います。すべての可能性を確実にテストするには、foo のバリエーションと bar のバリエーション (長さと内容) がある場所でテストを実行する必要があります。したがって、foo の x 個の数と、foo 内の Rectangle の 1 ~ x 個の数。

インターフェイスを実装する各オブジェクトは、bar 内の各 Rectangle でいくつかの計算を行い、それらのプロパティを変更するアルゴリズムを実行しています。各アルゴリズムは、大幅に異なる結果を生成します。

10 個の可能な foo オブジェクトと、bar 配列に 10 個の可能なオブジェクトを選択すると、何千もの書き込みが発生します! テストの。何千ものテストを手書きする必要はありません。


質問

可能なオブジェクトを取得し、結果を生成するすべての可能な構成でラントテストを実行するアルゴリズムを作成し、戻ってすべての結果が正しいことを手作業で確認するのは、私には逆行しすぎでしょうか? それは単体テストを行う間違った方法ですか?

結果を生成するアルゴリズムを実行するのは間違っていますか?出力を手で確認しますか?

私の他の考えは、アルゴリズムに可能なオブジェクトをフィードし、テスト ハーネス用にフォーマットされた xml または json を吐き出し、各テストを実行し、不足しているアサーション値を入力してからフィードすることです。

私の他の計画は、foo Rectangle のリストと、bar で使用される可能性のある Rectangle のリストを受け入れるアルゴリズムを作成し、そのアルゴリズムでテスト ハーネス (アサーションを含む) で動作する形式で JSON を生成することです。JSON を生成するアルゴリズムはアサーションを認識しないため、テスト ハーネスを介して送信する前にアサーションを手動で書き込みます。

これは一般的な方法ですか?


フィードバックをありがとう:)

4

1 に答える 1

2

実装で行っている計算の種類についての詳細がわからず、良い答えを思いつくのは簡単ではありませんが、ユニットテストに参加する意欲があることを賞賛しているので、とにかく最善を尽くします。答えが長くなりすぎないことを願っています。;)

0.大きな期待ですか?

正直なところ、あなたの質問に完全に一致する答えはおそらくありません-物事を行うための多くの方法は正しく仕事をします、そして単体テストに適用される唯一の基本的なルールはそれらがあなたのシステムを証明するのを確実に助けるべきであるということです安定しています。彼らがそうしないのなら、あなたはそれらをわざわざ書くべきではありません。しかし、それが、100万行の異なる入力値と出力値の組み合わせでExcelシートを作成し、それをCSV形式で単体テストのforループにフィードすることで実現できるとしたら...

OK、多分もっと良い方法があります。しかし、最終的には、これをどれだけ徹底的に実行したいか、およびテストを改善するためにすでに実行したことからどれだけ逸脱するかによって異なります。

1.いくつかの賢いお尻の発言の準備をしなさい

私が行間で読んだことから、テストを書く前にコードを書いたので、テスト容易性について考えるのに多くの時間を費やしていません。残念ながら、これは単体テストを行うための最良の方法ではありません。本番コードに追加する各行は、作成する前に、失敗した単体テストで常にカバーする必要があります。その方法でのみ、システムが機能することを常に確認できます-そしてそれは常にテスト可能です!面倒に聞こえますか?一度慣れれば、そうではありません。

ファンダメンタルズについてはあまり気にしませんが、ユニットテストに真剣に取り組んでいる場合は、今後のすべてのプロジェクトにTDDを適用することをお勧めします。開始するには、 cleancoders.comでTDDエピソードをご覧ください-おじさんボブは私よりもこれらのことを説明するのにはるかに優れた仕事をしており、彼は見るのが楽しいです(彼のデモはJavaですが、それはそれほど問題ではないはずです-TDDの基本原則はすべての言語に適用されます)。

その間、私はあなたの質問に基づいてまだいくつかの賢いお尻の発言をします。私を訴えなさい;)

2.賢いお尻の発言#1:テストする方法は?

テストの目的は、テストしているコードが正しく機能することを証明することであり、引数の可能なすべての組み合わせに対してそれを繰り返し証明することではないことを忘れないでください。コードが正しいことを証明するために、アサーションの数を必要最小限に抑える必要があります。

これにより、最初の質問に答えることができます。テストする各アルゴリズムの正当性を証明するためのテストは1つだけにする必要があります。 そのテスト内のアサーションには、入力値と出力値のさまざまな組み合わせを使用できます。

nullアルゴリズムごとにテストを追加する唯一の理由は、失敗をテストするときです。つまり、引数として渡すか、制約に違反するものを渡すとどうなりますか。失敗した場合にエラーをスローするたびに、別のテストでテストする必要があります。

ただし、もう少し複雑なのは、テストを書き始める抽象化のレベルを選択することです。通常、クラス内のメソッドごとにテストを作成する必要はありません。特にプライベートメソッドの場合はそうではありません。これは、TDDを適用するもう1つの理由です。つまり、各実装の詳細をテストするのではなく、システムの一部が実行することになっていることをテストするなど、外部から何をしようとしているのかを考えさせます。コーディングする前にテストする場合、プログラムが成長し、事態がより複雑になっていることに気付いたときに、あちこちにテストを追加するのは簡単です。「事後」にこれを行うことは常にはるかに困難です。

3. Smart-ass remark#2:何をテストしますか?

プログラム設計の目標は、ユニットをシステムの他の部分から可能な限り切り離すことです。これは、1つのユニット内の別の組み合わせに物の組み合わせを適用することはおそらく良い設計ではないことを意味します。他のすべてのものとは別に、テストしているユニットに実装されているコードのみをテストできるはずです。これの意味は

4.これをあなたの問題に適用するための微妙な試み

なぜ私はあなたにこれをすべて話しているのですか?あなたのアプローチは統合テストのようなものだと私には思えます。テストするブラックボックスがあり、そこから何億ものものが出てくる可能性があります。これは一部は問題ありませんが、それでもそのブラックボックスをできるだけ小さくするようにしてください。

さて、あなたがしている実際の数学については何も知らないので、これからいくつかの仮定を立て始めます。これらが合わない場合は申し訳ありませんが、コードサンプルを提供していただければ、情報を追加または変更させていただきます。

明らかな最初の推測:長方形barの座標値に基づいて、配列のすべてのメンバーに同じ計算を繰り返し適用しています。fooこれは、メソッドで実際に2つのことを実行していることを意味します。a)bar配列を反復処理することとb)式を適用することです。

public function foobar ( foo:Rectangle, bar:Array ) : void {
    for each ( var rect:Rectangle in bar) {
        // things done to rect based on foo
    }
}

この場合、アーキテクチャを簡単に改善できます。最初のステップは、式を分離することです。

public function foobar ( foo:Rectangle, bar:Array ) : void {
    for each ( var rect:Rectangle in bar) {
        applyFooValuesToRect( foo, rect);
    }
}

public function applyFooValuesToRect ( foo : Rectangle, rect : Rectangle ) : void {
    // things done to rect based on foo
} 

これで、実際にテストする必要があるのはapplyFooValuesToRectメソッドであることがわかります。これにより、突然、テストの作成が非常に簡単になります。

また、反復にバリエーションがあるかもしれないと想像することもできます。1つの実装はfooすべてに適用され、1つはいくつかの基準に一致し、正の一致にのみ適用されます。おそらく、各値にbar基づいてfooで一連の計算を実行します。 bar1つではなく2つの式を使用するなど。これがプロジェクトに当てはまる場合は、ストラテジーパターンを使用することで、APIを大幅に改善し、複雑さを軽減できます。40のバリエーションのそれぞれについて、実際の数式を、共通のFormulaインターフェイスを実装する個別のクラスにします。

public interface Formula {
    function applyFooToBar (foo:Rectangle, bar:Rectangle) : Rectangle;
}

public class FormulaOneImpl implements Formula {

    public function applyFooToBar (foo:Rectangle, bar:Rectangle) : Rectangle {
        // do things to bar
        return bar;
    }
}

public class FormulaTwoImpl implements Formula ... // etc.

これで、各数式を個別にテストし、返された値にアサーションを適用できます。

元のクラスは、次のタイプのフィールド変数を取りますFormula

public class MyGreatImpl implements OriginalInterface {
    public var formula:Formula;
    //..
    public function foobar (foo:Rectangle, bar:Array):void {
        for each (var rect:Rectangle in bar) formula.applyFooToBar (foo, rect);
    }
}

次に、インターフェイスを実装している限り、あらゆる種類の数式を渡すことができます。その結果、インターフェースを使用して、アルゴリズムの他のすべての部分をテストするためのモックオブジェクトを作成できるようになりました。Formulaモックオブジェクトが行う必要があるのは、呼び出されたことを確認しapplyFooToBar、各アサーションに設定した所定の値を返すことだけです。これにより、たとえば、配列の反復をテストするときに、実際に数式をテストしていないことを確認できます。

実際、これを他のものにも適用してみることができます。ACriteriaMatcherは、最初に、優れた戦略にもなります...

このようにコードを分解すると、同じ式に依存しているが、反復ループのバリエーションが異なるなど、複数の実装があることがわかります。これにより、の実装の数を減らすこともできます。最初のインターフェース-バリエーションがさまざまな戦略クラスにカプセル化されているためです。

少年、これは長いテキストです。私は今、暴言をやめます。私はこれについて少しお役に立てれば幸いです。私が自分の仮定からかけ離れている場合は、質問にコメントするか、もう一度編集してください。おそらく、考えられる解決策をもう少し絞り込むことができます。

于 2012-03-10T15:31:21.660 に答える