web-dev-qa-db-ja.com

コードが単体テストの一部として実行されているかどうかを判断する

ユニットテスト(nUnit)があります。呼び出しスタックの多くの層では、単体テストを介してメソッドが実行されている場合、メソッドは失敗します。

理想的には、このメソッドが依存しているオブジェクトをモックのようなものを使用してセットアップしますが、これはサードパーティのコードであり、私は多くの作業なしでそれを行うことはできません。

NUnit固有のメソッドをセットアップしたくありません-レベルが多すぎて、ユニットテストの方法が貧弱です。

代わりに、私はこのようなものをコールスタックに深く追加することを望みます

#IF DEBUG // Unit tests only included in debug build
if (IsRunningInUnitTest)
   {
   // Do some setup to avoid error
   }
#endif

それでは、IsRunningInUnitTestの書き方に関するアイデアはありますか?

追伸私はこれが素晴らしいデザインではないことを完全に承知していますが、私は思考選択肢よりも優れています。

94
Ryan

私はこれを以前にやったことがあります-私はそれをしている間、私は鼻をつかまなければなりませんでしたが、私はそれをしました。プラグマティズムは、常にドグマティズムを打ち負かしています。もちろん、isを回避するためにリファクタリングできる素晴らしい方法があれば、それは素晴らしいことです。

基本的に、NUnitフレームワークアセンブリが現在のAppDomainにロードされているかどうかを確認する「UnitTestDetector」クラスがありました。これを一度行うだけで、結果をキャッシュします。 glyいですが、シンプルで効果的です。

72
Jon Skeet

ジョンの考えを取り入れて、これが私が思いついたものです-

using System;
using System.Reflection;

/// <summary>
/// Detect if we are running as part of a nUnit unit test.
/// This is DIRTY and should only be used if absolutely necessary 
/// as its usually a sign of bad design.
/// </summary>    
static class UnitTestDetector
{

    private static bool _runningFromNUnit = false;      

    static UnitTestDetector()
    {
        foreach (Assembly assem in AppDomain.CurrentDomain.GetAssemblies())
        {
            // Can't do something like this as it will load the nUnit Assembly
            // if (assem == typeof(NUnit.Framework.Assert))

            if (assem.FullName.ToLowerInvariant().StartsWith("nunit.framework"))
            {
                _runningFromNUnit = true;
                break;
            }
        }
    }

    public static bool IsRunningFromNUnit
    {
        get { return _runningFromNUnit; }
    }
}

後ろにパイプで降りて、私たちはみんな、おそらくすべきではないことをしているときを認識するのに十分な大きさの少年です;)

69
Ryan

ライアンの答えから改作。これは、MS単体テストフレームワーク用です。

これが必要な理由は、エラー時にMessageBoxを表示するためです。しかし、私のユニットテストはエラー処理コードもテストするため、ユニットテストの実行時にメッセージボックスがポップアップすることは望ましくありません。

/// <summary>
/// Detects if we are running inside a unit test.
/// </summary>
public static class UnitTestDetector
{
    static UnitTestDetector()
    {
        string testAssemblyName = "Microsoft.VisualStudio.QualityTools.UnitTestFramework";
        UnitTestDetector.IsInUnitTest = AppDomain.CurrentDomain.GetAssemblies()
            .Any(a => a.FullName.StartsWith(testAssemblyName));
    }

    public static bool IsInUnitTest { get; private set; }
}

そして、それはユニットテストです:

    [TestMethod]
    public void IsInUnitTest()
    {
        Assert.IsTrue(UnitTestDetector.IsInUnitTest, 
            "Should detect that we are running inside a unit test."); // lol
    }
53
dan-gph

Tallsethと同様のアプローチを使用します

これは、キャッシングを含めるために簡単に変更できる基本コードです。別の良いアイデアは、セッターをIsRunningInUnitTestに追加し、UnitTestDetector.IsRunningInUnitTest = falseをプロジェクトのメインエントリポイントに呼び出して、コードの実行を回避することです。

public static class UnitTestDetector
{
    public static readonly HashSet<string> UnitTestAttributes = new HashSet<string> 
    {
        "Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute",
        "NUnit.Framework.TestFixtureAttribute",
    };
    public static bool IsRunningInUnitTest
    {
        get
        {
            foreach (var f in new StackTrace().GetFrames())
                if (f.GetMethod().DeclaringType.GetCustomAttributes(false).Any(x => UnitTestAttributes.Contains(x.GetType().FullName)))
                    return true;
            return false;
        }
    }
}
18

Ryanのソリューションを簡素化するには、次の静的プロパティを任意のクラスに追加するだけです。

    public static readonly bool IsRunningFromNUnit = 
        AppDomain.CurrentDomain.GetAssemblies().Any(
            a => a.FullName.ToLowerInvariant().StartsWith("nunit.framework"));
15
cdiggins

現在のProcessNameを確認すると便利かもしれません:

public static bool UnitTestMode
{
    get 
    { 
        string processName = System.Diagnostics.Process.GetCurrentProcess().ProcessName;

        return processName == "VSTestHost"
                || processName.StartsWith("vstest.executionengine") //it can be vstest.executionengine.x86 or vstest.executionengine.x86.clr20
                || processName.StartsWith("QTAgent");   //QTAgent32 or QTAgent32_35
    }
}

また、この関数はunittestで確認する必要があります。

[TestClass]
public class TestUnittestRunning
{
    [TestMethod]
    public void UnitTestRunningTest()
    {
        Assert.IsTrue(MyTools.UnitTestMode);
    }
}

参照:
Matthew Watson in http://social.msdn.Microsoft.com/Forums/en-US/csharplanguage/thread/11e68468-c95e-4c43-b02b-7045a52b407e/

11
Kiquenet

テストモードでは、Assembly.GetEntryAssembly()nullのようです。

_#IF DEBUG // Unit tests only included in debug build 
  if (Assembly.GetEntryAssembly() == null)    
  {
    // Do some setup to avoid error    
  }
#endif 
_

Assembly.GetEntryAssembly()nullである場合、Assembly.GetExecutingAssembly()はそうではないことに注意してください。

documentation の意味:マネージアセンブリがアンマネージアプリケーションから読み込まれた場合、GetEntryAssemblyメソッドはnullを返すことができます。

9

テスト中のプロジェクトのどこか:

public static class Startup
{
    public static bool IsRunningInUnitTest { get; set; }
}

ユニットテストプロジェクトのどこか:

[TestClass]
public static class AssemblyInitializer
{
    [AssemblyInitialize]
    public static void Initialize(TestContext context)
    {
        Startup.IsRunningInUnitTest = true;
    }
}

エレガント、ないしかし、簡単かつ高速です。 AssemblyInitializerはMSテスト用です。他のテストフレームワークにも同等のものがあると思います。

8
Edward Brey

デバッガーが接続されていない場合、起動時にlog4netのすべてのTraceAppendersを無効にするロジックをスキップするために、これをonlyを使用します。これにより、非デバッグモードで実行している場合でも、単体テストでResharperの結果ウィンドウにログを記録できます。

この関数を使用するメソッドは、アプリケーションの起動時またはテストフィクスチャの開始時に呼び出されます。

Ryanの投稿に似ていますが、LINQを使用し、System.Reflection要件を削除し、結果をキャッシュせず、(偶発的な)誤用を防ぐためにプライベートです。

    private static bool IsNUnitRunning()
    {
        return AppDomain.CurrentDomain.GetAssemblies().Any(Assembly => Assembly.FullName.ToLowerInvariant().StartsWith("nunit.framework"));
    }
3
Graeme Wicksted

Nunitフレームワークへの参照は、テストが実際に実行されていることを意味しません。たとえば、Unityでプレイモードテストをアクティブにすると、nunit参照がプロジェクトに追加されます。また、ゲームを実行すると参照が存在するため、UnitTestDetectorは正しく機能しません。

Nunitアセンブリを確認する代わりに、nunit apiに、テストを実行中のコードであるかどうかを確認するように依頼できます。

using NUnit.Framework;

// ...

if (TestContext.CurrentContext != null)
{
    // nunit test detected
    // Do some setup to avoid error
}

編集:

TestContextが自動的に生成される場合があります 必要な場合は注意してください。

1
ChessMax

私は最近この問題を抱えて不幸でした。私はわずかに異なる方法でそれを解決しました。最初に、nunitフレームワークがテスト環境の外部にロードされることは決してないと仮定したくありませんでした。特に、開発者が自分のマシンでアプリを実行するのが心配でした。そこで、代わりに呼び出しスタックを調べました。次に、テストコードがリリースバイナリに対して実行されないという前提を立てることができたため、このコードがリリースシステムに存在しないことを確認しました。

internal abstract class TestModeDetector
{
    internal abstract bool RunningInUnitTest();

    internal static TestModeDetector GetInstance()
    {
    #if DEBUG
        return new DebugImplementation();
    #else
        return new ReleaseImplementation();
    #endif
    }

    private class ReleaseImplementation : TestModeDetector
    {
        internal override bool RunningInUnitTest()
        {
            return false;
        }
    }

    private class DebugImplementation : TestModeDetector
    {
        private Mode mode_;

        internal override bool RunningInUnitTest()
        {
            if (mode_ == Mode.Unknown)
            {
                mode_ = DetectMode();
            }

            return mode_ == Mode.Test;
        }

        private Mode DetectMode()
        {
            return HasUnitTestInStack(new StackTrace()) ? Mode.Test : Mode.Regular;
        }

        private static bool HasUnitTestInStack(StackTrace callStack)
        {
            return GetStackFrames(callStack).SelectMany(stackFrame => stackFrame.GetMethod().GetCustomAttributes(false)).Any(NunitAttribute);
        }

        private static IEnumerable<StackFrame> GetStackFrames(StackTrace callStack)
        {
            return callStack.GetFrames() ?? new StackFrame[0];
        }

        private static bool NunitAttribute(object attr)
        {
            var type = attr.GetType();
            if (type.FullName != null)
            {
                return type.FullName.StartsWith("nunit.framework", StringComparison.OrdinalIgnoreCase);
            }
            return false;
        }

        private enum Mode
        {
            Unknown,
            Test,
            Regular
        }
1
tallseth

魅力のように働く

if (AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(x => x.FullName.ToLowerInvariant().StartsWith("nunit.framework")) != null)
{
    fileName = @"C:\Users\blabla\xxx.txt";
}
else
{
    var sfd = new SaveFileDialog
    {     ...     };
    var dialogResult = sfd.ShowDialog();
    if (dialogResult != DialogResult.OK)
        return;
    fileName = sfd.FileName;
}

1
tom nobleman

私はコードでVBで以下を使用して、単体テストでaeが発生したかどうかを確認しました。

    If Not Application.ProductName.ToLower().Contains("test") then
        ' Do something 
    End If
0
TomShantisoft

通常、コードはWindowsフォームアプリケーションのメイン(GUI)スレッドで実行され、確認できるテストでの実行中にコードの動作が異なることを考慮する

if (SynchronizationContext.Current == null)
{
    // code running in a background thread or from within a unit test
    DoSomething();
}
else
{
    // code running in the main thread or any other thread where
    // a SynchronizationContext has been set with
    // SynchronizationContext.SetSynchronizationContext(synchronizationContext);
    DoSomethingAsync();
}

Guiアプリケーションでfire and forgotしたいコードにこれを使用していますが、単体テストでは、アサーションの計算結果が必要な場合があり、実行中の複数のスレッドを台無しにしたくありません。

MSTestで機能します。コードがテストフレームワーク自体を確認する必要がないという利点と、特定のテストで非同期動作が本当に必要な場合は、独自のSynchronizationContextを設定できます。

これは、コードがスレッド内で実行される可能性があるため、OPが要求するDetermine if code is running as part of a unit testに対する信頼性の高い方法ではないことに注意してください。新しいものを開始する必要はないかもしれません)。

0

ユニットテスターで実行している場合、Application.Currentはnullです。少なくとも、MS Unitテスターを使用するWPFアプリの場合。必要に応じて簡単に作成できます。また、コードでApplication.Currentを使用する際に留意すべき点があります。

0
Glaucus