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


105

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

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

nUnit固有のメソッドをセットアップしたくありません。レベルが多すぎて、単体テストを行う方法としては不十分です。

代わりに、私がしたいことは、このようなものをコールスタックの深いところに追加することです

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

では、IsRunningInUnitTestの書き方について何かアイデアはありますか?

PS私はこれが素晴らしいデザインではないことを完全に認識していますが、私は代替案よりも優れていると思います


5
単体テストでサードパーティのコードを直接または間接的にテストすることはできません。テスト中のメソッドをサードパーティの実装から分離する必要があります。
Craig Stuntz、2010

14
はい-私はそれを知っています-アイデアの世界では、しかし私たちは時々物事について少し実用的でなければなりませんか?
Ryan

9
クレイグのコメントに戻って-それが本当かどうかわからない。私の方法が特定の方法で動作するサードパーティのライブラリに依存している場合、これはテストの一部ではありませんか?サードパーティアプリが変更された場合、テストを失敗させます。モックを使用している場合は、実際の動作ではなく、サードパーティ製アプリの動作方法に対するテストを行います。
Ryan

2
ライアン、サードパーティの動作に関する仮定をテストできますが、それは別のテストです。独自のコードを分離してテストする必要があります。
Craig Stuntz、2010

2
私はあなたの言っていることを理解しますが、些細な例以外では、あなたは大量の(巨大な)仕事について話しているでしょうし、テストでのチェックの仮定が実際のメソッドでの仮定と同じであることを保証するものは何もありません。うーん-ブログ投稿についての議論は、私が考えているときにメールを撮ります。
Ryan

回答:


80

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

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


UnitTestDetectorに関するサンプルはありますか?MSTestと同様の?
Kiquenet 2013年

4
@Kiquenet:AppDomain.GetAssemblies関連するアセンブリを使用して確認するだけだと思います。MSTestの場合は、どのアセンブリが読み込まれているかを確認する必要があります。例として、ライアンの答えを見てください。
Jon Skeet 2013年

これは私にとって良いアプローチではありません。コンソールアプリからUnitTestメソッドを呼び出していますが、UnitTestアプリだと思っています。
Bizhan

1
@ビザン:その時はかなり特殊な状況にいることをお勧めします。もっと一般的な答えが期待できるはずはありません。特定の要件をすべて備えた新しい質問をすることができます。(たとえば、「コンソールアプリケーションから呼び出すコード」と「テストランナー」の違いは何ですか?コンソールアプリケーションと他のコンソールベースのテストランナーをどのように区別したいですか?)
Jon Skeet

74

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

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; }
    }
}

後ろにパイプを下ろすと、私たち全員が、やるべきではないことをしているときにそれを認識するのに十分なほど大きな男の子です;)


2
+1良い答え。:、以下を参照してくださいが、これはかなり単純化することができるstackoverflow.com/a/30356080/184528
cdiggins

私がこれを書いた特定のプロジェクトは.NET 2.0(そして今もそうです!)だったので、linqはありませんでした。
ライアン

これは私にとってはうまく機能しましたが、アセンブリ名が変わったようです。Kiquenetのソリューション
The_Black_Smurf 2017年

私は
travis

私のために働く、私はユニットテストでのみ発生するかみそりで.NETコア3のバグを回避する必要があります。
jjxtra

62

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

これが必要な理由は、エラー時にMessageBoxを表示するためです。しかし、私の単体テストではエラー処理コードもテストします。単体テストを実行するときに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
    }

8
メッセージボックスの問題を解決してこのハックを無効にし、より多くのユニットテストケースを提供するより良い方法があります。私はICommonDialogsを呼び出すインターフェイスを実装するクラスを使用しています。実装クラスは、すべてのポップアップダイアログ(メッセージボックス、ファイルダイアログ、カラーピッカー、データベース接続ダイアログなど)を表示します。メッセージボックスを表示する必要があるクラスは、ユニットテストでモックできるコンストラクターパラメーターとしてICommonDiaglogsを受け入れます。おまけ:予想されるMessageBox呼び出しでアサートできます。
トニー・オハガン

1
@Tony、いい考え。それが明らかに最良の方法です。その時何を考えていたのか分かりません。依存関係の注入は、当時はまだ新しいものでした。
dan-gph 2013年

3
真剣に、人々は依存関係注入について、そして第二に、モックオブジェクトについて学びます。依存性注入はプログラミングに革命をもたらします。
dan-gph 2014年

2
UnitTestDetector.IsInUnitTestを「return true」として実装し、ユニットテストに合格します。;)単体テストは不可能に思われる面白いことの1つ。
Samer Adra

1
Microsoft.VisualStudio.QualityTools.UnitTestFrameworkが機能しなくなりました。それをMicrosoft.VisualStudio.TestPlatform.TestFrameworkに変更しました-再び機能します。
Alexander

21

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

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

2
dan-gphの答えとほぼ同じです(ただし、nunitではなくVSツールセットを探していました)。
ライアン

18

私はトールセットと同様のアプローチを使用しています

これは、キャッシングを含めるように簡単に変更できる基本コードです。別の良いアイデアは、コードの実行を回避するために、setterをプロジェクトのメインエントリポイントに追加し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;
        }
    }
}

私はこのアプローチが投票数の多い回答よりも好きです。ユニットテストアセンブリはユニットテスト中にのみ読み込まれ、プロセス名も開発者ごとに異なる可能性があると想定するのは安全ではないと思います(たとえば、一部はR#テストランナーを使用します)。
EM0

このアプローチは機能しますが、IsRunningInUnitTestゲッターが呼び出されるたびにこれらの属性を探します。パフォーマンスに影響を与える場合があります。AssemblyNameの確認は1回しか行われないため、コストが低くなります。パブリックセッターのアイデアは良いですが、この場合、UnitTestDetectorクラスは共有アセンブリに配置する必要があります。
セルゲイ

13

おそらく、現在の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);
    }
}

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


|| processName.StartsWith("testhost") // testhost.x86VS 2019
Kiquenet

9

テストモードで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()ですnullAssembly.GetExecutingAssembly()ではありません。

ドキュメントは言う:GetEntryAssemblyメソッドが返すことができnullマネージアセンブリがアンマネージアプリケーションからロードされたとき。


8

テストされているプロジェクトのどこか:

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;
    }
}

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


1
テストしているコードが追加のAppDomainを作成IsRunningInUnitTestする場合、それらのAppDomainでtrueに設定されません。
Edward Brey 2015

ただし、共有アセンブリを追加するか、各ドメインでIsRunningInUnitTestを宣言することで簡単に解決できます。
セルゲイ

3

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

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

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

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

3

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

nunitアセンブリをチェックする代わりに、nunit apiにテストを実行中のコードかどうかを今すぐチェックするように依頼できます。

using NUnit.Framework;

// ...

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

編集:

TestContextは、必要に応じて自動的に生成される場合があることに注意してください。


2
ここにコードをダンプしないでください。それが何をするか説明してください。
nkr 2018

2

これを使用するだけです:

AppDomain.CurrentDomain.IsDefaultAppDomain()

テストモードでは、falseを返します。


1

私は最近この問題に遭遇して不幸でした。少し違う方法で解決しました。まず、私は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
        }

リリースバージョンの出荷中にデバッグバージョンをテストするという考えは、一般的には悪い考えです。
Patrick M

1

魅力のように働く

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

単体テストはアプリケーションのエントリポイントをスキップします。少なくともwpfでは、winformsとコンソールアプリケーションmain()は呼び出されていません。

実行時よりもmainメソッドが呼び出された場合、それ以外の場合は単体テストモードになります。

public static bool IsUnitTest { get; private set; } = true;

[STAThread]
public static void main()
{
    IsUnitTest = false;
    ...
}

0

コードが通常は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();
}

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

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

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


0

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


0

ユニットテストでAEかどうかを確認するために、コードのVBで以下を使用しました。具体的には、テストでWordを開く必要はありませんでした

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

0

リフレクションとこのようなものを使用するのはどうですか:

var underTest = Assembly.GetCallingAssembly()!= typeof(MainForm).Assembly;

呼び出し元のアセンブリは、テストケースがある場所であり、MainFormの代わりに、テスト対象のコード内にあるいくつかの型を使用します。


-3

あなたがクラスをテストしているときも本当に簡単な解決策があります...

テストするクラスに次のようなプロパティを指定するだけです。

// For testing purposes to avoid running certain code in unit tests.
public bool thisIsUnitTest { get; set; }

これで、単体テストで「thisIsUnitTest」ブール値をtrueに設定できるため、スキップするコードに次の行を追加します。

   if (thisIsUnitTest)
   {
       return;
   } 

アセンブリの検査よりも簡単で高速です。Ruby on Railsを思い出して、テスト環境にいるかどうかを確認します。


1
クラスの動作を変更するためにテスト自体に依存しているため、あなたはここで反対票を投じたと思います。
Riegardt Steyn 2014

1
これは、ここにある他のすべての回答と同じです。
DonO、
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.