web-dev-qa-db-ja.com

Visual Studioでの単体テストの実行順序の制御

さて、これで良い情報を探しました。一度初期化されると、変更できない(または変更したくない)プロパティを設定する静的クラスを呼び出す一連の単体テストがあります。

私の問題は、テストを実行するための設定順序を強制できないことです。可能であれば、静的プロパティが信頼できる方法で設定されるようにそれらを実行でき、それらをアサートできますが、残念ながらMicrosoft.VisualStudio.TestTools.UnitTestingフレームワークは一見ランダムな順序でそれらを実行するだけです。

だから、私はこれを見つけました http://msdn.Microsoft.com/en-us/library/Microsoft.visualstudio.testtools.unittesting.priorityattribute.aspx これは、「この属性はテストシステムで使用されます。カスタム目的でユーザーに提供されます。」え?それでは何がいいの?このすばらしい属性を活用するために、独自のテストラッパーを作成することを期待していますか(そのレベルの努力をしたい場合は簡単に自分で作成できます...)

だから、暴言の十分な;結論として、ユニットテストの実行順序を制御する方法はありますか?

[TestMethod]
[Priority(0)]

などは動作しないように見えますが、これは理にかなっています。

また、「分離違反」についてコメントしないでください。 TestClassは、個々のTestMethodsではなく、私がテストしているものを分離します。とにかく、各テストは問題なく独立して実行できます。静的クラスを破棄する方法がないため、ランダムな順序で一緒に実行することはできません。

ああ、「Ordered Test」についても知っています。

61
iGanja

テストを1つの巨大なテストにマージすると機能します。テストメソッドをより読みやすくするために、次のようなことができます

[TestMethod]
public void MyIntegratonTestLikeUnitTest()
{
    AssertScenarioA();

    AssertScenarioB();

    ....
}

private void AssertScenarioA()
{
     // Assert
}

private void AssertScenarioB()
{
     // Assert
}

実際、あなたが持っている問題は、おそらく実装のテスト容易性を改善する必要があることを示唆しています。

48
Gang Gao

Playlistを使用できます

テスト方法を右クリック->プレイリストに追加->新しいプレイリスト

実行順序は、プレイリストに追加した順になりますが、変更したい場合はファイルがあります

enter image description here

108
HB MAAM

ClassInitialize属性メソッドについて言及している人はいません。属性は非常に単純です。

[ClassInitialize()]または[TestInitialize()]属性でマークされたメソッドを作成して、単体テストを実行する環境の側面を準備します。これの目的は、単体テストを実行するための既知の状態を確立することです。たとえば、[ClassInitialize()]または[TestInitialize()]メソッドを使用して、テストで使用する特定のデータファイルをコピー、変更、または作成できます。

[ClassCleanup()]または[TestCleanUp{}]属性でマークされたメソッドを作成して、テストの実行後に環境を既知の状態に戻します。これは、フォルダー内のファイルの削除、またはデータベースを既知の状態に戻すことを意味する場合があります。この例は、注文入力アプリケーションで使用されるメソッドをテストした後、在庫データベースを初期状態にリセットすることです。

  • [ClassInitialize()]クラスの最初のテストを実行する前に、ClassInitializeを使用してコードを実行します。

  • [ClassCleanUp()]クラス内のすべてのテストの実行後にコードを実行するには、ClassCleanupを使用します。

  • [TestInitialize()]TestInitializeを使用して、各テストを実行する前にコードを実行します。

  • [TestCleanUp()]TestCleanupを使用して、各テストの実行後にコードを実行します。

9
MattyMerrix

コメンターが既に指摘したように、他のテストに依存するテストを持つことは、設計上の欠陥を指摘しています。それにもかかわらず、それを達成する方法があります。以前に尋ねられた質問ここで回答したように、順序付けられた単体テストを作成できます。これは基本的に、テストシーケンス。

MSDNのガイドは次のとおりです。 http://msdn.Microsoft.com/en-us/library/ms182631.aspx

6
Honza Brestan

Visual Studioテストフレームワークが提供するOrdered Test機能については既に説明したので、それは無視します。また、この静的クラスをテストするためにあなたが達成しようとしていることは「悪い考え」であることを認識しているようですので、私はそれを無視します。

代わりに、テストが必要な順序で実行されることを実際に保証できる方法に焦点を当てましょう。 1つのオプション(@gaog提供)は「1つのテストメソッド、多くのテスト関数」で、TestMethod属性でマークされた単一の関数内から希望する順序でテスト関数を呼び出します。これが最も簡単な方法であり、唯一の欠点は、最初に失敗したテスト関数が残りのテスト関数の実行を妨げることですです。

状況の説明で、これはあなたが使用することをお勧めする解決策です。

太字の部分に問題がある場合は、組み込みのデータ駆動型テスト機能を活用することで、分離されたテストを順番に実行できます。より複雑で少し汚い感じがしますが、仕事は完了です。

要するに、テストを実行する必要がある順序と、実際にテスト機能を含む関数の名前を制御するデータソース(CSVファイルやデータベーステーブルなど)を定義します。次に、そのデータソースをデータドリブンテストにフックし、シーケンシャルリードオプションを使用して、個々のテストとして必要な順序で関数を実行します。

[TestClass]
public class OrderedTests
{
    public TestContext TestContext { get; set; }

    private const string _OrderedTestFilename = "TestList.csv";

    [TestMethod]
    [DeploymentItem(_OrderedTestFilename)]
    [DataSource("Microsoft.VisualStudio.TestTools.DataSource.CSV", _OrderedTestFilename, _OrderedTestFilename, DataAccessMethod.Sequential)]
    public void OrderedTests()
    {
        var methodName = (string)TestContext.DataRow[0];
        var method = GetType().GetMethod(methodName);
        method.Invoke(this, new object[] { });
    }

    public void Method_01()
    {
        Assert.IsTrue(true);
    }

    public void Method_02()
    {
        Assert.IsTrue(false);
    }

    public void Method_03()
    {
        Assert.IsTrue(true);
    }
}

この例では、TestList.csvというサポートファイルがあり、出力にコピーされます。次のようになります。

TestName
Method_01
Method_02
Method_03

テストは、指定した順序で実行され、通常のテスト分離で実行されます(つまり、テストが失敗した場合、残りは実行されますが、静的クラスは共有されます)。

上記は本当に基本的なアイデアに過ぎません。本番環境で使用する場合、テストを実行する前にテスト関数名とその順序を動的に生成します。おそらく、見つけたPriorityAttributeといくつかの単純なリフレクションコードを利用して、クラス内のテストメソッドを抽出し、適切に順序付けしてから、その順序をデータソースに書き込みます。

5
Todd Bowles

これでわかるはずですが、純粋主義者は、順序付けられたテストを実行することは禁じられていると言います。単体テストの場合はそうかもしれません。 MSTestおよび他の単体テストフレームワークは、純粋な単体テストを実行するために使用されますが、UIテスト、完全な統合テストも実行できます。ユニットテストフレームワークと呼ぶべきではないかもしれませんし、必要に応じて使用するべきかもしれません。それはほとんどの人がとにかくすることです。

私はVS2015を実行していますが、UIテスト(Selenium)を実行しているため、特定の順序でテストを実行する必要があります。

Priority-何もしません この属性はテストシステムでは使用されません。カスタム目的でユーザーに提供されます。

orderedtest-動作しますが、お勧めしません:

  1. orderedtestテストを実行する順序でリストしたテキストファイル。メソッド名を変更する場合は、ファイルを修正する必要があります。
  2. テストの実行順序はクラス内で尊重されます。どのクラスが最初にテストを実行するかを順序付けることはできません。
  3. orderedtestファイルは、デバッグまたはリリースの構成にバインドされます
  4. 複数のorderedtestファイルを使用できますが、特定のメソッドを異なるorderedtestファイルで繰り返すことはできません。したがって、1つのorderedtestファイルをデバッグ用に、もう1つをリリース用に使用することはできません。

このスレッドの他の提案は興味深いものですが、テストエクスプローラーでテストの進行状況を追跡する機能を失います。

純粋主義者が助言する解決策が残っていますが、実際には有効な解決策があります:宣言順に並べ替えます

MSTestエグゼキューターは、相互運用を使用して宣言の順序を取得します。このトリックは、Microsoftがテストエグゼキューターコードを変更するまで機能します。

つまり、最初に宣言されたテストメソッドは、2番目に宣言されたテストメソッドの前に実行されます。

作業を楽にするために、宣言の順序は、テストエクスプローラーに表示されるアルファベット順と一致する必要があります。

  • A010_FirstTest
  • A020_SecondTest
  • A100_TenthTest

古いテスト済みのルールを強くお勧めします。

  • 後でテストメソッドを挿入する必要があるため、10のステップを使用します。
  • テスト番号の間に寛大なステップを使用することで、テストの番号を付け直す必要がなくなります
  • 10を超えるテストを実行している場合は、3桁を使用してテストに番号を付けます
  • 100を超えるテストを実行している場合は、4桁を使用してテストに番号を付けます

非常に重要

宣言順にテストを実行するには、テストエクスプローラーでRun Allを使用する必要があります。

3つのテストクラスがあるとします(私の場合、Chrome、Firefox、およびEdgeのテスト)。特定のクラスを選択し、右クリック選択したテストの実行を行うと、通常、最後の場所で宣言されたメソッドが実行されます。

繰り返しますが、前にも言ったように、宣言された順序リストされた順序は一致するはずです。

4

ビルドマシンでmstest.exe引数を調整する必要がない、またはクラス内で順序付けられていないものと順序付けられたものを混合する必要がないなど、何らかの理由で、MS Ordered Testsフレームワークから独立した順序付けられたテストをセットアップおよび実行するために使用できるクラスを次に示します。

元のテストフレームワークでは、順序付けられたテストのリストが単一のテストとしてのみ表示されるため、[TestInitalize()] Init()などのinit/cleanupは、セット全体の前後でのみ呼び出されます。

使用法:

        [TestMethod] // place only on the list--not the individuals
        public void OrderedStepsTest()
        {
            OrderedTest.Run(TestContext, new List<OrderedTest>
            {
                new OrderedTest ( T10_Reset_Database, false ),
                new OrderedTest ( T20_LoginUser1, false ),
                new OrderedTest ( T30_DoLoginUser1Task1, true ), // continue on failure
                new OrderedTest ( T40_DoLoginUser1Task2, true ), // continue on failure
                // ...
            });                
        }

実装:

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace UnitTests.Utility
{    
    /// <summary>
    /// Define and Run a list of ordered tests. 
    /// 2016/08/25: Posted to SO by crokusek 
    /// </summary>    
    public class OrderedTest
    {
        /// <summary>Test Method to run</summary>
        public Action TestMethod { get; private set; }

        /// <summary>Flag indicating whether testing should continue with the next test if the current one fails</summary>
        public bool ContinueOnFailure { get; private set; }

        /// <summary>Any Exception thrown by the test</summary>
        public Exception ExceptionResult;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="testMethod"></param>
        /// <param name="continueOnFailure">True to continue with the next test if this test fails</param>
        public OrderedTest(Action testMethod, bool continueOnFailure = false)
        {
            TestMethod = testMethod;
            ContinueOnFailure = continueOnFailure;
        }

        /// <summary>
        /// Run the test saving any exception within ExceptionResult
        /// Throw to the caller only if ContinueOnFailure == false
        /// </summary>
        /// <param name="testContextOpt"></param>
        public void Run()
        {
            try
            {
                TestMethod();
            }
            catch (Exception ex)
            {
                ExceptionResult = ex;
                throw;
            }
        }

        /// <summary>
        /// Run a list of OrderedTest's
        /// </summary>
        static public void Run(TestContext testContext, List<OrderedTest> tests)
        {
            Stopwatch overallStopWatch = new Stopwatch();
            overallStopWatch.Start();

            List<Exception> exceptions = new List<Exception>();

            int testsAttempted = 0;
            for (int i = 0; i < tests.Count; i++)
            {
                OrderedTest test = tests[i];

                Stopwatch stopWatch = new Stopwatch();
                stopWatch.Start();

                testContext.WriteLine("Starting ordered test step ({0} of {1}) '{2}' at {3}...\n",
                    i + 1,
                    tests.Count,
                    test.TestMethod.Method,
                    DateTime.Now.ToString("G"));

                try
                {
                    testsAttempted++;
                    test.Run();
                }
                catch
                {
                    if (!test.ContinueOnFailure)
                        break;
                }
                finally
                {
                    Exception testEx = test.ExceptionResult;

                    if (testEx != null)  // capture any "continue on fail" exception
                        exceptions.Add(testEx);

                    testContext.WriteLine("\n{0} ordered test step {1} of {2} '{3}' in {4} at {5}{6}\n",
                        testEx != null ? "Error:  Failed" : "Successfully completed",
                        i + 1,
                        tests.Count,
                        test.TestMethod.Method,
                        stopWatch.ElapsedMilliseconds > 1000
                            ? (stopWatch.ElapsedMilliseconds * .001) + "s"
                            : stopWatch.ElapsedMilliseconds + "ms",
                        DateTime.Now.ToString("G"),
                        testEx != null
                            ? "\nException:  " + testEx.Message +
                                "\nStackTrace:  " + testEx.StackTrace +
                                "\nContinueOnFailure:  " + test.ContinueOnFailure
                            : "");
                }
            }

            testContext.WriteLine("Completed running {0} of {1} ordered tests with a total of {2} error(s) at {3} in {4}",
                testsAttempted,
                tests.Count,
                exceptions.Count,
                DateTime.Now.ToString("G"),
                overallStopWatch.ElapsedMilliseconds > 1000
                    ? (overallStopWatch.ElapsedMilliseconds * .001) + "s"
                    : overallStopWatch.ElapsedMilliseconds + "ms");

            if (exceptions.Any())
            {
                // Test Explorer prints better msgs with this hierarchy rather than using 1 AggregateException().
                throw new Exception(String.Join("; ", exceptions.Select(e => e.Message), new AggregateException(exceptions)));
            }
        }
    }
}
4
crokusek

テストの順序については説明しませんが、申し訳ありません。他の人はすでにそれをしました。また、「順序付けられたテスト」について知っている場合-これは、問題に対するMS VSの応答です。これらの順序付きテストは楽しいものではないことを知っています。しかし、彼らはそれが「それ」であると考え、それについてMSTestには本当に何もありません。

私はあなたの仮定の一つについて書いています:

静的クラスを破棄する方法がないためです。

静的クラスがコードの外部のプロセス全体の外部状態を表さない限り(たとえば、残りのコードによってP/InvokedされるアンマネージネイティブDLLライブラリの状態)、there is no wayが正しくないという仮定。

静的クラスがこれを参照している場合、申し訳ありませんが、あなたは完全に正しいです、この答えの残りの部分は無関係です。それでも、あなたがそれを言わなかったように、私はあなたのコードが「管理されている」と仮定します。

AppDomainのことを考えて確認してください。必要になることはめったにありませんが、おそらくこれを使用したい場合です。

新しいAppDomainを作成し、そこでテストをインスタンス化し、そこでテストメソッドを実行できます。マネージコードで使用される静的データはそこで分離され、完了するとAppDomainをアンロードでき、静的データを含むすべてのデータが蒸発します。次に、次のテストで別のappdomainを初期化します。

これは、追跡する必要がある外部状態がない限り機能します。 AppDomainsは、マネージメモリのみを分離します。ネイティブDLLはプロセスごとにロードされ、その状態はすべてのAppDomainによって共有されます。

また、appdomainsを作成/破棄すると、テストが遅くなります。また、子appdomainでのアセンブリの解決に問題があるかもしれませんが、合理的な量の再利用可能なコードで解決可能です。

また、テストデータを子AppDomainに渡したり、子AppDomainから返したりする際に小さな問題が発生する場合があります。渡されたオブジェクトは、何らかの方法でシリアル化可能であるか、MarshalByRefなどである必要があります。クロスドメインの対話は、ほとんどIPCに似ています。

ただし、ここで注意してください、それは100%管理された会話になります。細心の注意を払ってAppDomainセットアップに少し作業を追加すると、デリゲートを渡してターゲットドメインで実行することもできます。次に、毛深いクロスドメイン設定を行う代わりに、テストを次のようなものにラップできます。

void testmethod()
{
    TestAppDomainHelper.Run( () =>
    {
        // your test code
    });
}

あるいは

[IsolatedAppDomain]
void testmethod()
{
    // your test code
}

テストフレームワークがそのようなラッパー/拡張機能の作成をサポートしている場合。いくつかの初期調査と作業の後、それらを使用することはほとんど簡単です。

2
quetzalcoatl

このトピックはほぼ6年前であり、Visual Studioの新しいバージョンがありますが、とにかく返信します。 Visual Studio 19でその順序の問題が発生したため、メソッド名の前に次のようにアルファベット順に大文字(小さな文字も追加できます)を追加することで解決しました。

[TestMethod]
        public void AName1()
        {}
[TestMethod]
        public void BName2()
        {}

等々。これは魅力的ではないことは知っていますが、Visualがテストエクスプローラーでテストをアルファベット順に並べ替えているように見えます。コードでの記述方法は関係ありません。この場合、プレイリストは機能しませんでした。

これが役立つことを願っています。

0
Taverna Joe