12

Seleniumを使用してJavaWebアプリのHTMLページ(実際にはJSP)をテストしています。私のWebアプリでは、各ページにアクセスするためのフローが必要です(これは小さなオンラインゲームのWebアプリです)。たとえば、ページBにアクセスするには、ページAに移動し、テキストを入力してボタンを押すと、ページにアクセスできます。 B.明らかに、ページAが正しく機能していることを確認するためのテストがすでにいくつかあります。

ページAのテストが実行された後、ページBのテストが実行されることを確認するためにさらにテストを記述できるようにしたいと思います(アプリの残りの部分についても同様です)。つまり、テストで何らかの順序を定義します。

過去数日間にテストについて多くのことを読んだ後、私はこの特定の主題に関して興味深いものを見つけることができません。したがって、私は今アドバイスを求めています。

私が特定した可能な解決策:

  1. (同じテストクラスで)ページAのテストメソッドを定義し、次にテストBのテストメソッドを定義します。次に、テストメソッドの実行を順序付けます。しかし、JUnitは(ただしTestNGは)テストメソッドの実行順序を許可しないことを知っています。SOの質問selenium-junit-tests-how-do-i-run-tests-within-a-test-in-sequential-orderを参照してください。

  2. すべてのテスト(ページA、ページBなど)を1つのテスト方法にグループ化します。しかし、私はそれが悪いことを読みました。SOの質問を参照してください:junit-one-test-case-per-method-or-multiple-test-cases-per-method。セレンテストをするとき、それは悪いですか?私はそれをしているいくつかのコードを見たので、そうではないかもしれないと思います。

  3. すべてのテスト(ページA、ページBなど)を1つのテストメソッドにグループ化しますが、JUnitのErrorCollectorクラスを使用します。ErrorCollectorを使用すると、同じメソッドで順序付きチェックを実行でき、失敗した場合は特定のエラーメッセージが表示されます。メソッド(したがってチェック)は最後まで実行されます。この解決策は私にはあまりにも「残酷」に思えます。

  4. JUnitのTestSuiteクラスを使用します。テストクラスがスイートで定義されている順序で、スイートにリストされているテストを実行します。したがって、これには、テストクラス(たとえばTestA)でページAをテストするための独立したテストメソッドが含まれ、次にテストクラス(たとえばTestB)でページBをテストするためのすべてのテストメソッドが含まれます。次に、それらを@SuiteClasses({TestA.class、TestB.class、TestC.class、...})などのテストスイートに挿入します。

  5. JUnitのTestSuiteクラスをJUnitのErrorCollectorクラスと組み合わせて使用​​します。まあ、私たちができるので、あなたは異なるクラスのページごとにグループテストをしたいかもしれません、そしてそのグループページの上にErrorCollectorを使って「ゾーン」をテストします。このソリューションは、非常に密度の高いWebページやその他の理由がある場合に非常に役立つ場合があります。

  6. 非常に急進的:TestNGなどの別のツールを使用して、テストメソッドの順序付けなどの機能にアクセスします。

注:最後の解決策(TestNGに移行する)を推奨する人もいると思いますが、JUnitに関連する他のアイデア/意見も聞きたいです。のように、(何らかの理由で)別のテストフレームワークに移行できないチームで作業している場合、このテストの順序の問題にどのように対処しますか?

4

5 に答える 5

8

なぜ移行するのですか?JUnitを単体テストに使用し、別のフレームワークを高レベルのテストに使用できます。あなたの場合、それは一種の受け入れ、機能的、またはエンドツーエンドであり、どのように名前を付けるかはそれほど重要ではありません。しかし重要なのは、これらのテストはユニットではないことを理解することです。それらは異なるルールに固執します:それらはより複雑で、より長くそしてより少ない頻度で実行され、複雑なセットアップ、外部依存関係を必要とし、散発的に失敗するかもしれません。彼らのために別のフレームワーク(または別のプログラミング言語)を使用してみませんか?

可能なバリアントは次のとおりです。

別のフレームワークを追加することがオプションではない場合:JUnitのオプションをさらに列挙すると、想像できます=)フローのテストスクリプト全体を1つのテストメソッドに入れ、テストコードを「ドライバー」に編成します。つまり、エンドツーエンドのテストでは、アプリケーションやSelenium APIのメソッドを直接呼び出すのではなく、APIの複雑さを隠し、何が起こっているのか、何が期待されているのかを示すステートメントのように見えるDriverコンポーネントのメソッドにラップします。例を見てください:

@Test 
public void sniperWinsAnAuctionByBiddingHigher() throws Exception {
    auction.startSellingItem();

    application.startBiddingIn(auction);
    auction.hasReceivedJoinRequestFrom(ApplicationRunner.SNIPER_XMPP_ID);

    auction.reportPrice(1000, 98, "other bidder");
    application.hasShownSniperIsBidding(auction, 1000, 1098);

    auction.hasReceivedBid(1098, ApplicationRunner.SNIPER_XMPP_ID);

    auction.reportPrice(1098, 97, ApplicationRunner.SNIPER_XMPP_ID);
    application.hasShownSniperIsWinning(auction, 1098);

    auction.announceClosed();
    application.hasShownSniperHasWonAuction(auction, 1098);
} 

スニペットは、「テストによって導かれる成長するオブジェクト指向ソフトウェア」から取られています。この本は本当に素晴らしいので、読むことを強くお勧めします。

これは、実際のXMPP接続、Openfireジャバーサーバー、およびWindowLickerSwingGUIテストフレームワークを使用する実際のエンドツーエンドテストです。しかし、ドライバーコンポーネントにオフロードされた場合、これらすべてのもの。そして、あなたのテストでは、さまざまなアクターがどのように通信するかを確認します。そして、注文されます。アプリケーションが入札を開始した後、オークションサーバーが参加リクエストを受信したことを確認し、次にオークションサーバーに新しい価格を報告し、UIなどに反映されていることを確認します。コード全体はgithubで入手できます。

githubの例は複雑です。これは、アプリケーションが本の例で通常発生するほど簡単ではないためです。しかし、その本は徐々にそれを与え、私は本のガイドに従ってアプリケーション全体を最初から構築することができました。実際、このような徹底的で完全な例を示すのは、TDDと自動開発者テストについて私が今まで読んだ唯一の本です。そして、私はそれらをかなりたくさん読みました。ただし、ドライバーのアプローチではテストユニットが作成されないことに注意してください。複雑さを隠すことができます。また、他のフレームワークでも使用できます(使用する必要があります)。必要に応じて、テストを一連のステップに分割するための追加の可能性を提供します。ユーザーが読み取り可能なテストケースを作成する。テストデータをCSV、Excelテーブル、XMLファイル、またはデータベースに外部化して、テストをタイムアウトします。外部システム、サーブレット、およびDIコンテナと統合するため。テストグループを個別に定義して実行する。よりユーザーフレンドリーなレポートなどを提供します。

そして、すべてのテストユニットを作成することについて。数学や文字列処理などのユーティリティライブラリのようなものを除いては不可能です。完全に単体テストされたアプリケーションがある場合は、すべてのアプリケーションをテストしていないか、どのテストが単体テストで何がそうでないかを理解していないことを意味します。最初のケースは問題ないかもしれませんが、カバーされていないものはすべて、開発者、テスター、ユーザー、または誰でも手動でテストおよび再テストする必要があります。それは非常に一般的ですが、カジュアルな決定よりも意識的な決定である方が良いです。なぜすべてをユニットテストできないのですか?

ユニットテストの定義はたくさんあり、それは聖戦につながります)私は次のことを好みます:「ユニットテストはプログラムユニットを分離してテストすることです」。「ねえ、ユニットは私のアプリケーションです!私はログインをテストし、それは単純なユニット機能です」と言う人もいます。しかし、孤立して隠れている語用論もあります。なぜ他のユニットテストと異なる必要があるのですか?それは私たちの最初のセーフティネットだからです。彼らは速くなければなりません。頻繁にコミットし(たとえば、gitに)、少なくともそれらを実行します各コミットの前。しかし、「単体」テストの実行には5分かかると想像してください。それらを実行する頻度を減らすか、コミットする頻度を減らすか、一度に1つのテストケースまたは1つのテストメソッドを実行するか、2分ごとに5分でテストが完了するのを待ちます。その5分間で、コーディングホラーに移動し、次の2時間を費やします=)そして、単体テストが散発的に失敗することはありません。彼らがそうするなら、あなたは彼らを信用しないでしょう。したがって、分離:ユニットテストから速度の低下と散発的な障害の原因を分離する必要があります。したがって、分離とは、単体テストで以下を使用してはならないことを意味します。

  • ファイルシステム
  • ネットワーク、ソケット、RMIなど
  • GUI
  • マルチスレッド
  • テストフレームワークを期待し、Hamcrestのような単純なライブラリをサポートする外部ライブラリ

また、単体テストはローカルである必要があります。スイート全体の半分ではなく、コーディングから2分以内に欠陥を作成したときに、1つほどのテストが失敗するようにしたい場合。つまり、単体テストでステートフル動作をテストすることは非常に限られています。前提条件に到達するために5つの状態遷移を行うテストセットアップを行うべきではありません。最初のトランジションで失敗すると、後続のトランジションで少なくとも4つのテストが失敗し、6番目のトランジションで現在作成しているもう1つのテストが失敗するためです。また、重要なアプリケーションには、非常に多くのフローと状態遷移が含まれています。したがって、これは単体テストできません。同じ理由で、単体テストでは、データベース、静的フィールド、Springコンテキストなどで変更可能な共有状態を使用してはなりません。これが、JUnitがすべてのテストメソッドに対してテストクラスの新しいインスタンスを作成する理由です。

つまり、どのように再コーディングしても、Webアプリを完全に単体テストすることはできません。フロー、JSP、サーブレットコンテナなどがあるためです。もちろん、この定義は無視できますが、非常に便利です)単体テストを他のテストと区別することが有用であり、この定義がそれを達成するのに役立つことに同意する場合は、別のフレームワークまたは少なくとも別のアプローチを選択します。単体ではないテストの場合は、別の種類のテスト用に別のスイートを作成します。

願わくば、これが役立つでしょう)

于 2012-09-13T12:25:14.227 に答える
2

Runnerおそらく他Runnerをラップし、yo9uが定義した基準に従ってテストをソートする独自の実装を行うことができます。これは本当に必要な場合です。

しかし、私はそれがそれほど必要であるとは思いません。ページAが機能しない場合、Bへのパスも機能しないことを理解しています。したがって、最初にテストAを実行してから、テストA->Bを実行します。しかし、それは本当に意味がありますか?たとえば、テストA-> Bが最初に実行され、ページAに到達できないために失敗した場合、テストAを検証する他のテストも失敗します。したがって、両方のテストは失敗し、idはテストの順序に依存しません。

ただし、テストAをテストBのセットアップ操作として使用する場合は、非常に悪い方法です。テストAのロジックをテストBの開始として使用できますが、2つのテストを結合しないでください。明らかな理由の1つは、これによりデバッグが非常に困難になることです。テストA->Bをデバッグするには、AテストとA-> Bテストの両方を実行する必要があります。つまり、おそらくすべてのテストを実行する必要があります(少なくとも1つのテストケース内で)。

于 2012-09-13T10:56:58.513 に答える
1

フレームワーク呼び出しCucumberを使用します。その行動主導の開発。つまり、基本的に、フローに依存しないテスト関数を作成し、機能を使用してテストのフローを制御できます。

例ページ1、2入力を入力し、Enterをクリックして、ページ2に何かがロードされていることを確認します。

次に、これを機能ファイルに入れます。

Given Page 1
   Then I enter text1
   Then I enter text2
   Then I click button
   Then I see page 2

そして、あなたのコードでは、これらのステップを実装するクラスを持つことができます。また、cucumberフレームワークでは、アノテーションを使用して、テストコードと機能ファイルの間のマッピングを示すことができます。

...
@Given("^Page 1$")
public void iLoadPage1() {
  WebDriver driver = new ....
  driver.go('URL');
}

@Given("I enter (.*)$")
public void iEnterTest(String txt) {
  ...
}

...
于 2012-09-13T11:15:26.947 に答える
1

JUnitは、実際にはフロー/統合レベルのテスト用に設計されていません。

そのような状況のために、テストの実行順序を保証し、すべてのテストでテストクラスの同じインスタンスを再利用して、ステップの値を別のステップに渡すことができる独自のランナーを設計しました。

(はい、単体テストには悪い習慣ですが、jUnitで実行されている場合でも、単体テストについては話していません)。

別のツール(キュウリ、フィットネス、TestNGなど)を使用することも良い解決策ですが、プロジェクトにはテストツールが多すぎるなどの問題があります。

于 2012-09-13T21:44:10.973 に答える
1

動作する可能性のある別のオプションは、JUnitパラメーター化をテストに適用することです。私の現在の理解では、実装のパラメーターは常に提供された順序で実行されます。

その概念を使用すると、JUnit実装にコンストラクター引数としてURLを受け入れさせ、提供されたパラメーターに基づいてテストを内部的にフォークすることができます。

同じWebDriver参照を使用していることを確認するには、おそらく静的な@ BeforeClass /@AfterClass宣言である必要があります。これにより、パラメータを相互に連鎖させて、「前のテストからXページにいます。ここでタスクYを実行します。このテストの最後にZページに移動します。または状態A"。

ユニットレベルのテストでは、このソリューションは悪い形になると思いますが、Seleniumのようなツールを統合すると、統合テストレベルで動作し始めます。私自身はこの概念にかなり慣れていませんが、統合テストレベルでは、条件が依存するため、モジュール性のルールは少し曖昧になっているようです。

気になったので試してみました。テストに関連してアプリケーションを静的リソースとして扱うことができると仮定すると、私が思っていたように動作します。

package demo.testing;

import java.util.List;

import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Lists;

@RunWith(Parameterized.class)
public class SequentialParams {

    private static SystemState state;

    @BeforeClass
    public static void validateBeforeState() {
        state = new SystemState();

        Assert.assertFalse(state.one);
        Assert.assertFalse(state.two);
        Assert.assertFalse(state.three);
        Assert.assertFalse(state.four);
    }

    @Parameters
    public static Object buildParameters() {
        Runnable reset = new Runnable() {

            public void run() {
                state.one = false;
                state.two = false;
                state.three = false;
                state.four = false;
            }
        };
        Runnable oneToTrue = new Runnable() {

            public void run() {
                state.one = true;
            }
        };
        Runnable twoToTrue = new Runnable() {

            public void run() {
                state.two = true;
            }
        };
        Runnable threeToTrue = new Runnable() {

            public void run() {
                state.three = true;
            }
        };
        Runnable fourToTrue = new Runnable() {

            public void run() {
                state.four = true;

            }
        };


        Predicate<SystemState> oneIsTrue = new Predicate<SequentialParams.SystemState>() {
            public boolean apply(SystemState input) {
                return input.one;
            }
        };
        Predicate<SystemState> twoIsTrue = new Predicate<SequentialParams.SystemState>() {
            public boolean apply(SystemState input) {
                return input.two;
            }
        };
        Predicate<SystemState> threeIsTrue = new Predicate<SequentialParams.SystemState>() {
            public boolean apply(SystemState input) {
                return input.three;
            }
        };
        Predicate<SystemState> fourIsTrue = new Predicate<SequentialParams.SystemState>() {
            public boolean apply(SystemState input) {
                return input.four;
            }
        };

        Predicate<SystemState> oneIsFalse = new Predicate<SequentialParams.SystemState>() {
            public boolean apply(SystemState input) {
                return !input.one;
            }
        };
        Predicate<SystemState> twoIsFalse = new Predicate<SequentialParams.SystemState>() {
            public boolean apply(SystemState input) {
                return !input.two;
            }
        };
        Predicate<SystemState> threeIsFalse = new Predicate<SequentialParams.SystemState>() {
            public boolean apply(SystemState input) {
                return !input.three;
            }
        };
        Predicate<SystemState> fourIsFalse = new Predicate<SequentialParams.SystemState>() {
            public boolean apply(SystemState input) {
                return !input.four;
            }
        };
        List<Object[]> params = Lists.newArrayList();

        params.add(new Object[]{Predicates.and(oneIsFalse, twoIsFalse, threeIsFalse, fourIsFalse), oneToTrue, Predicates.and(oneIsTrue, twoIsFalse, threeIsFalse, fourIsFalse)});
        params.add(new Object[]{Predicates.and(oneIsTrue, twoIsFalse, threeIsFalse, fourIsFalse), twoToTrue, Predicates.and(oneIsTrue, twoIsTrue, threeIsFalse, fourIsFalse)});
        params.add(new Object[]{Predicates.and(oneIsTrue, twoIsTrue, threeIsFalse, fourIsFalse), threeToTrue, Predicates.and(oneIsTrue, twoIsTrue, threeIsTrue, fourIsFalse)});
        params.add(new Object[]{Predicates.and(oneIsTrue, twoIsTrue, threeIsTrue, fourIsFalse), fourToTrue, Predicates.and(oneIsTrue, twoIsTrue, threeIsTrue, fourIsTrue)});

        params.add(new Object[]{ Predicates.and(oneIsTrue, twoIsTrue, threeIsTrue, fourIsTrue), reset, Predicates.and(oneIsFalse, twoIsFalse, threeIsFalse, fourIsFalse)});

        params.add(new Object[]{Predicates.and(oneIsFalse, twoIsFalse, threeIsFalse, fourIsFalse), threeToTrue, Predicates.and(oneIsFalse, twoIsFalse, threeIsTrue, fourIsFalse)});
        params.add(new Object[]{Predicates.and(oneIsFalse, twoIsFalse, threeIsTrue, fourIsFalse), oneToTrue, Predicates.and(oneIsTrue, twoIsFalse, threeIsTrue, fourIsFalse)});
        params.add(new Object[]{Predicates.and(oneIsTrue, twoIsFalse, threeIsTrue, fourIsFalse), fourToTrue, Predicates.and(oneIsTrue, twoIsFalse, threeIsTrue, fourIsTrue)});
        params.add(new Object[]{Predicates.and(oneIsTrue, twoIsFalse, threeIsTrue, fourIsTrue), twoToTrue, Predicates.and(oneIsTrue, twoIsTrue, threeIsTrue, fourIsTrue)});


        return params;
    }

    Predicate<SystemState> verifyStartState;
    Runnable changeState;
    Predicate<SystemState> verifyEndState;

    public SequentialParams(Predicate<SystemState> pre, Runnable task, Predicate<SystemState> post) {
      verifyStartState = pre;
      changeState = task;
      verifyEndState = post;
    }

    @Test
    public void perform() {
        Assert.assertTrue(verifyStartState.apply(state));
       changeState.run();
       Assert.assertTrue(verifyEndState.apply(state));
    }


    private static class SystemState {
        public boolean one = false;
        public boolean two = false;
        public boolean three = false;
        public boolean four = false;

    }

}
于 2015-10-05T10:32:02.840 に答える