web-dev-qa-db-ja.com

単体テストのいくつかのコードを自動生成することは実行可能で便利ですか?

今日の初めに、特定の実際のユースケースに基づいて、実現可能性と有用性を確認したいというアイデアを思いつきました。この質問は、Javaコードのかなりのチャンクを特徴としていますが、VM内で実行されているすべての言語に適用できます。おそらく外部にも適用できます。実際のコードはありますが、言語固有のものは何も使用していません。なので、主に擬似コードとして読んでください。

アイデア
コードベースとのhuman対話に基づいてコードを自動生成するいくつかの方法で追加することで、ユニットテストの煩雑さを軽減します。これはTDDの原則に反することを理解していますが、最初にコードを作成してからすぐにテストを作成するよりもTDDを行う方が優れていることを証明した人はいないと思います。これはTDDに適合するように改造することもできますが、それは私の現在の目標ではありません。

それがどのように使用されることが意図されているかを示すために、ここで私のクラスの1つをコピーして、ユニットテストを行う必要があります。

_public class PutMonsterOnFieldAction implements PlayerAction {
    private final int handCardIndex;
    private final int fieldMonsterIndex;

    public PutMonsterOnFieldAction(final int handCardIndex, final int fieldMonsterIndex) {
        this.handCardIndex = Arguments.requirePositiveOrZero(handCardIndex, "handCardIndex");
        this.fieldMonsterIndex = Arguments.requirePositiveOrZero(fieldMonsterIndex, "fieldCardIndex");
    }

    @Override
    public boolean isActionAllowed(final Player player) {
        Objects.requireNonNull(player, "player");
        Hand hand = player.getHand();
        Field field = player.getField();
        if (handCardIndex >= hand.getCapacity()) {
            return false;
        }
        if (fieldMonsterIndex >= field.getMonsterCapacity()) {
            return false;
        }
        if (field.hasMonster(fieldMonsterIndex)) {
            return false;
        }
        if (!(hand.get(handCardIndex) instanceof MonsterCard)) {
            return false;
        }
        return true;
    }

    @Override
    public void performAction(final Player player) {
        Objects.requireNonNull(player);
        if (!isActionAllowed(player)) {
            throw new PlayerActionNotAllowedException();
        }
        Hand hand = player.getHand();
        Field field = player.getField();
        field.setMonster(fieldMonsterIndex, (MonsterCard)hand.play(handCardIndex));
    }
}
_

次のテストの必要性を観察できます。

  • 有効な入力を使用したコンストラクターテスト
  • 無効な入力によるコンストラクターテスト
  • isActionAllowed有効な入力でテスト
  • isActionAllowed無効な入力でのテスト
  • performAction有効な入力でテスト
  • performAction無効な入力でのテスト

私のアイデアは、無効な入力を使用したisActionAllowedテストに主に焦点を当てています。これらのテストを書くのは楽しいことではありません。いくつかの条件を確認し、それが本当にfalseを返すかどうかを確認する必要があります。これはperformActionに拡張でき、例外をスローする必要があります場合。

私のアイデアの目的は、(IDEのGUIを介して)あなたが望むことを示すことによって、それらのテストを生成することです特定のブランチに基づいてテストを生成します。

例による実装

  1. ユーザーが「ブランチのコードを生成if (handCardIndex >= hand.getCapacity())」をクリックします。
  2. ツールは、それが成り立つケースを見つける必要があります。

    (最終的に投稿が煩雑になる可能性があるため、関連するコードを追加していません)

  3. ブランチを無効にするには、ツールはhandCardIndexおよびhand.getCapacity()を見つけて、_>=_の条件が満たされるようにする必要があります。

  4. 容量が1以上のPlayerを使用してHandを構築する必要があります。
  5. capacityHandprivate intは少なくとも1である必要があることに気付きます。
  6. それを1に設定する方法を検索します。幸いにも、capacityを引数として取るコンストラクタを見つけます。これには1を使用します。
  7. Playerインスタンスを正常に構築するには、さらにいくつかの作業を行う必要があります。これには、ソースコードを調べることで確認できる制約を持つオブジェクトの作成が含まれます。
  8. 可能な限り最小の容量のhandを見つけ、それを構築できます。
  9. テストを無効にするには、_handCardIndex = 1_を設定する必要があります。
  10. テストを構築し、false(ブランチの戻り値)であることをアサートします

ツールが機能するために何が必要ですか?
正しく機能するためには、すべてのソースコード(JDKコードを含む)をスキャンして、すべての制約を把握する機能が必要です。オプションで、これはjavadocを介して実行できますが、すべての制約を示すために常に使用されるわけではありません。 some試行錯誤することもできますが、コンパイルされたクラスにソースコードをアタッチできない場合は、ほとんど停止します。

次に、配列を含め、primitive型が何であるかについての基本的な知識が必要です。そして、何らかの形の「変更ツリー」を構築できる必要があります。ツールは、正しいテストケースを取得するために、特定の変数を別の値に変更する必要があることを認識しています。したがって、明らかにリフレクションを使用せずに、変更可能なすべての方法をリストする必要があります。

このツールで置き換えられないのは、特定のメソッドが実際に機能するときにあらゆる種類の条件をテストする、調整された単体テストを作成する必要があることです。制約を無効にするときにメソッドをテストするためだけに使用されます。

私の質問

  • そのようなツールを作成していますか実現可能ですか?それはうまくいくでしょうか、それとも明らかな問題がありますか?
  • そのようなツールは有用でしょうか?これらのテストケースを自動的に生成することも役に立ちますか?さらに便利なことをするように拡張できますか?
  • 偶然にも、そのようなプロジェクトはすでに存在していて、私は車輪を再発明するでしょうか?

役に立たないことが証明されていても、そのようなものを作成することがまだ可能であれば、私はそれを楽しみのために検討します。便利だと思われる場合は、時間に応じてオープンソースプロジェクトを作成する可能性があります

私の例で使用されているPlayerおよびHandクラスの背景情報をさらに検索している人は、 this repository を参照してください。執筆時点では、PutMonsterOnFieldActionはまだリポジトリにアップロードされていませんが、ユニットテストが完了したら、これは完了します。

5
skiwi

すべてソフトウェアにすぎないので、十分な努力を払えば可能です;-)。コード分​​析を適切に行う方法をサポートする言語では、それも実行可能でなければなりません。

有用性については、実装方法によっては、単体テストの自動化がある程度役立つと思います。事前にどこに行きたいかを明確にし、このタイプのツールの制限を十分に認識する必要があります。

ただし、テストするコードがテストをリードするため、記述したフローには大きな制限があります。これは、コードを開発する際の推論のエラーがテストにも含まれる可能性が高いことを意味します。最終結果はおそらく、基本的に、コードが実行することを確認するのではなく、コードが「実行すること」を確認するテストになります。これは実際に完全に役に立たないわけではありません。後でコードを使用してコードが以前に実行したことを確認できるためですが、機能テストではないため、このようなテストに基づいてコードをテストすることはできません。 (null処理などのいくつかの浅いバグをまだキャッチしている可能性があります)役に立たないわけではありませんが、「実際の」テストを置き換えることはできません。それでも「実際の」テストを作成する必要がある場合は、努力する価値はないかもしれません。

ただし、これで少し異なるルートに進むことができます。 1つ目は、おそらくコードに関するいくつかの一般的なアサーションを定義した後、データをAPIにスローして、どこで破損するかを確認することです。それは基本的には ファズテスト

もう1つは、テストを生成することですが、アサーションなしで、基本的にステップ10をスキップします。したがって、ツールが有用なテストケースを決定し、特定の呼び出しに対して予想される答えをテスターに​​尋ねる「APIクイズ」に終わります。そうすれば、実際にもう一度テストすることになります。ただし、それでも完全ではありません。コード内の機能的なシナリオを忘れた場合、ツールは魔法のようにそれを見つけられず、すべての仮定が削除されるわけではありません。 > =の場合、代わりにコードがif (handCardIndex > hand.getCapacity())であったとすると、このようなツールはそれ自体では決してそれを理解できません。 TDDに戻りたい場合は、インターフェースのみに基づいてテストケースを提案できますが、一部の機能を推測できるコードさえないため、機能的には不完全です。

あなたの主な問題は常に1になります。コードのエラーをテストに引き継ぐこと、および2.機能の完全性。どちらの問題もある程度抑制でき、決してなくすことはできません。常に要件に戻って座って、すべてが実際に正しくテストされていることを確認する必要があります。コードカバレッジは100%のカバレッジを示すため、ここで明らかに危険なのは誤った安心感です。遅かれ早かれ誰かがその間違いをするでしょう、利益がそのリスクを上回るかどうかを決めるのはあなた次第です。私見それは、品質と開発速度の間の古典的なトレードオフに要約されます。

1
AVee

[〜#〜] tldr [〜#〜]:悪い考えです(理由については、以下を参照してください)。

まず、私はこれに対処したいと思いました:

これはTDDの原則に反することを理解していますが、最初にコードを作成してからすぐにテストを作成するよりもTDDを行う方が優れていることを証明した人はいないと思います。これはTDDに適合するように改造することもできますが、それは私の現在の目標ではありません。

TDD is最初にコードを書くよりも優れています(はい、それは証明されています)。

ここにいくつかの利点があります(多くのオンライン記事や本でこれらについても読むことができます):

  • TDDは、不要なものを書かないことを保証します(例:「テストがパスするための最小限のコードを書く」)
  • TDDは、APIのインターフェースが、実装者の観点からではなく、クライアントの観点から最適化されることを保証します。 1つ目は、クライアントコードに対して、コードが使いやすくなるようにすることです。 2番目は、モジュールのパブリックAPIを実装の詳細で汚染する傾向があります(これを防ぐように注意していても)。
  • TDDは、アルゴリズムの抽象的なアイデアではなく、仕様に合わせてコードを書くことを保証します
  • 最初にコードを書くと、コードが機能すると思われる多くの状況が見つかり、「手が届きにくいコーナーケースのためだけに追加のテストを書く理由はありません」(「開発者の怠惰」とも呼ばれます)、「非常に悪い誤訳」 YAGNI」、「プログラマの言い訳8」)。 TDDには言い訳はありません。あなたがチームで働き、同僚が言い訳をしてあなたを**** t闘わせるまで、または彼らを闘わせるためにあなたを呼ぶまで、それはそれほど聞こえないかもしれません。
  • TDDは労力を最小限に抑えます(通常、最初にコードを記述する場合、開発ループは「実装、テストの記述、テストのパスの変更/修正」です。TDDでは、「テストの記述、テストがパスするまでの実装」です。より短い)。

そのようなツールを作成することは可能ですか?それはうまくいくでしょうか、それともいくつかの明らかな問題がありますか?

十分なリソースがあれば、そのようなツールを作成することは理論的には可能です。

実際には、一般的な実装に完全に適合するようなものを作成できるかどうかは疑問です。単純なケースで何かを作成するのは十分簡単ですが、内部で他の25のAPIを呼び出すAPIをテストすることを検討してください(それぞれがアルゴリズムの循環的複雑度に追加されます)。このツールは、今日の市場で最高のスタティックアナライザーよりもはるかに多くのことを行う必要があります。

明らかな問題は次のとおりですテストは、(クライアントの)要件を意味のある方法でカバーしません-代わりに、実装のすべてのケースをカバーします。

正しいユニットテストでは、状況に応じて、コードのユニットが想定されていることをテストする必要があります。あなたが言及したように生成されたテストは、コードがそれがimplementedを実行する(そしてコードが到達可能であることを)実行することをテストします。これは、仕様とは関係ありません。

そのようなツールは役に立ちますか?

部分的に(機能が制限されています)。まれなケースの単体テストを作成するのに必要な時間を短縮できます。

これらのテストケースを自動的に生成することも有用ですか?さらに便利なことをするように拡張できますか?

あんまり。私は、ユーザーが生成したいケースを選択して、一度に1つのテストケースを自動的に生成すると便利だと思います(ただし、すべてのケースを生成すると、信号/ノイズ比が非常に低くなります-生成されたほとんどのコードを無関係として破棄する必要があります) 。

1
utnapistim

私は最近、生成されたテストについていくつかの再調査を行いました。不明なコードのテストを生成するには、主に3つの方法があるようです。 3つのアプローチはすべて、現在の動作(または仕様)が意図されているか、少なくとも受け入れられていることを前提としています。したがって、テストで特性化できます。

3つのアプローチは次のとおりです。

分析なし

コードはまったく分析されません。メソッドはランダムなシーケンスで呼び出され、すべての中間結果は後の回帰テストで検証されます。

このアプローチは randoop または evosuite によって実装されます。

静的分析

コードデータと制御フローが分析されます。一部のツールは、すべてのブランチに接触するテストデータを作成しようとするだけで、特定の仕様に準拠するようにコードを検証するツールもあります。分析では、特定のテストデータを使用して、メソッドによって変更された状態も分析する必要があります。両方の分析結果を使用して、テストを生成できます(テストデータを使用したセットアップ、分析された状態変化のアサート)。

これは、ユーザーにとって最も便利なアプローチのようです。また、問題に最も近いものです。 Symbolic Pathfinder はこのアプローチのツールのようですが、まだ本番環境には対応していません。

動的解析

コードは実行時に分析されません(デバッグ、プロファイリング)。一般的なアプローチでは、特定のメソッド呼び出し(呼び出しの前後の状態を含む)をキャプチャして、テストにシリアル化します。

Testrecorder と呼ばれるこのアプローチ用のツールを開発しました。

質問

そのようなツールを作成することは可能ですか?それはうまくいくでしょうか、それともいくつかの明らかな問題がありますか?

上記のいくつかの回答が指摘するように、常に正しい解決策はあり得ません。しかし、私はあなたの効率を高める解決策があるかもしれないと思います。

そのようなツールは役に立ちますか?これらのテストケースを自動的に生成することも有用ですか?さらに便利なことをするように拡張できますか?

これはシナリオによって異なります。コード(TDD)と共にテストを作成する方法は、コードを最低品質に保つための良い方法だと思います。

しかし、時々私は大規模なレガシーコードベースに直面しています。そのようなコードに多くの時間を費やすことを望んでいる人はいませんが、そのようなコードに新しいコードを導入するリスクは非常に高いです。このシナリオで生成されたテストを使用することは、妥当なトレードオフになると思います。

1
CoronA

これらの主な質問について:

これらのテストケースを自動的に生成することも有用ですか?さらに便利なことをするように拡張できますか?偶然にも、そのようなプロジェクトはすでに存在していて、私は車輪を再発明するでしょうか?

私は大胆にそう主張します。そうです、テストケースを生成することは有用でありえます、そうです、そのようなプロジェクトはすでに存在し、使用されています。ただし、質問で尋ねられた方法、つまりTDDの代替としてはそれほど有用ではありません。そうではありません。

これは他の答えの1つで言われました:

正しいユニットテストでは、状況に応じて、コードのユニットが想定どおりに動作するかどうかをテストする必要があります。あなたが言及したように生成されたテストは、コードが実行するために実装されていることをコードが実行することをテストします(そしてコードが到達可能であることをテストします)。これは、仕様とは関係ありません。

私はほとんど同意します。これが単体テストの一般的な使用方法であり、TDDでの使用方法です。ただし、これがすべてのテストに使用されるわけではありません。

既存の単体テストなしで大規模なコードベースを継承した状況を想定します。あなたはその振る舞いについて知りませんし、あなたが導入しようとしている変更が既存の振る舞いを壊すかどうかは確かにわかりません。既存のコードベースの現在の動作をキャプチャするテストを生成するのは素晴らしいことではないでしょうか?

それは確かです。これらの種類のテストはregressionテストと呼ばれ、この場合、テストケースを生成すると便利です。実際にこれを実行するツールは実際に存在します。おそらく最も人気のあるものは、商用ツール AgitarOne とその 回帰テスト生成機能 です。他の選択肢もあります。

これが役立つ別の使用例は、いくつかの一般的な契約に違反していないかどうかを確認することです。一般的な規約として、a)nullパラメータを指定しなかった場合、コードでnullポインタ例外がスローされないこと、およびb)equalsメソッドを実装している場合、equalsメソッドコントラクトで必要なルールに従う必要があることが考えられます。 。フィードバックを利用したランダムテスト生成を使用してこのような問題を検出する無料のツール Randoop など、このためのツールも存在します。

これらは、生成されたテストケースで有用性が見られるいくつかの例です。ただし、前の回答で指摘したように、それはではありませんたとえば、TDDのテストや、一般的な。これらはややコーナーケースです。一般的なケースでは、自分でテストを作成する必要があります。

1
eis

コンピュータサイエンスの現在の理論的知識で話すのは現実的ではありません。

これを確認する最も簡単な方法は、これが証明されていない推測をコードに挿入することです。 (それらの多くがあります)。コードがこのブランチまたはそのブランチに入ることができるかどうかはわかりません。また、どの値がブランチに入れるかもわかりません。

そのため、ソースコードにアクセスできる場合でも、特定のブランチに入るテストケースを実際に生成することはできません。

そして、それが単なるソフトウェアであると考える場合、十分に努力すれば得られます。推測のエンコードの長さを数回増やした後、検索スペースが指数関数的に増加することを覚えておいてください。次に、別の方法を見つける必要があります。

0
randomA