코드가 단위 테스트의 일부로 실행 중인지 확인


105

단위 테스트 (nUnit)가 있습니다. 호출 스택 아래의 많은 계층에서 단위 테스트를 통해 실행중인 경우 메서드가 실패합니다.

이상적으로는이 방법이 의존하는 객체를 설정하기 위해 조롱과 같은 것을 사용하는 것이 좋지만 이것은 타사 코드이며 많은 작업 없이는 할 수 없습니다.

nUnit 특정 방법을 설정하고 싶지 않습니다. 여기에는 레벨이 너무 많고 단위 테스트를 수행하는 방법이 좋지 않습니다.

대신에 제가하고 싶은 것은 콜 스택에 다음과 같은 것을 추가하는 것입니다.

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

IsRunningInUnitTest를 작성하는 방법에 대한 아이디어가 있습니까?

추신 나는 이것이 훌륭한 디자인이 아니라는 것을 완전히 알고 있지만 대안보다 낫다고 생각 합니다.


5
단위 테스트에서 타사 코드를 직접 또는 간접적으로 테스트해서는 안됩니다. 테스트중인 메서드를 타사 구현에서 격리해야합니다.
Craig Stuntz

14
예-저도 알아요-아이디어의 세계에서,하지만 때로는 우리가 무언가에 대해 약간 실용적이어야합니다.
Ryan

9
Craig의 의견으로 돌아와서-그것이 사실인지 확실하지 않습니다. 내 방법이 특정 방식으로 작동하는 타사 라이브러리에 의존한다면 이것이 테스트의 일부가 아니어야합니까? 타사 앱이 변경되면 테스트에 실패하고 싶습니다. 타사 앱이 실제로 작동하는 방식이 아니라 작동 방식에 대해 모의 테스트를 사용하는 경우.
Ryan

2
Ryan, 타사 동작에 대한 가정을 테스트 할 수 있지만 이는 별도의 테스트입니다. 자신의 코드를 격리하여 테스트해야합니다.
Craig Stuntz

2
나는 당신의 말을 얻었지만 사소한 예를 제외하고는 많은 양의 작업에 대해 이야기 할 것이며 테스트에서 확인한 가정이 실제 방법의 가정과 동일하다는 것을 보장 할 수있는 것은 없습니다 . 흠-블로그 게시물에 대한 토론 제 생각에 제 생각이 모이면 이메일을 보내 드릴게요.
Ryan

답변:


80

전에 해본 적이 있습니다.하는 동안 코를 잡아야했지만 해냈습니다. 실용주의는 매번 독단주의를 능가합니다. 물론 를 방지하기 위해 리팩토링 할 수있는 좋은 방법 있다면 좋을 것입니다.

기본적으로 NUnit 프레임 워크 어셈블리가 현재 AppDomain에로드되었는지 확인하는 "UnitTestDetector"클래스가 있습니다. 이 작업을 한 번만 수행 한 다음 결과를 캐시하면됩니다. 추악하지만 간단하고 효과적입니다.


UnitTestDetector에 대한 샘플이 있습니까? MSTest와 유사합니까?
Kiquenet 2013-06-05

4
@Kiquenet : AppDomain.GetAssemblies관련 어셈블리를 사용 하고 확인하는 것 뿐이라고 생각 합니다. MSTest의 경우 어떤 어셈블리가로드되는지 살펴 봐야합니다. 예를 들어 Ryan의 대답을보십시오.
Jon Skeet

이것은 나에게 좋은 접근 방식이 아닙니다. 콘솔 앱에서 UnitTest 메서드를 호출하고 있으며 UnitTest 앱이라고 생각합니다.
Bizhan

1
@Bizhan : 나는 당신이 다소 특수한 상황에 있다고 제안하고 더 일반적인 대답이 작동 할 것이라고 기 대해서는 안됩니다. 모든 특정 요구 사항에 대해 새로운 질문을 할 수 있습니다. (예를 들어 "콘솔 응용 프로그램에서 호출하는 코드"와 "테스트 실행기"의 차이점은 무엇입니까? 콘솔 응용 프로그램과 다른 콘솔 기반 테스트 실행기를 어떻게 구분 하시겠습니까?)
Jon Skeet

74

Jon의 생각을 받아들이면 이것이 제가 생각 해낸 것입니다.

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가 없습니다.
Ryan

이것은 나를 위해 일하는 데 사용되지만 그 이후 어셈블리 이름이 변경된 것 같습니다. 나는 Kiquenet의 솔루션으로
The_Black_Smurf

나는 트래비스 CI는 모든 것을 동결, 빌드에 대한 로깅을 해제했다
jjxtra

나를 위해 일하고, 단위 테스트에서만 발생하는 면도기로 .NET 코어 3 버그를 해킹해야합니다.
jjxtra

62

Ryan의 답변에서 수정되었습니다. 이것은 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
MessageBox 문제를 해결하고이 해킹을 무효화하고 더 많은 단위 테스트 사례를 제공하는 더 나은 방법이 있습니다. ICommonDialogs라는 인터페이스를 구현하는 클래스를 사용합니다. 구현 클래스는 모든 팝업 대화 상자 (MessageBox, 파일 대화 상자, 색상 선택기, 데이터베이스 연결 대화 상자 등)를 표시합니다. 메시지 상자를 표시해야하는 클래스는 단위 테스트에서 모의 ​​할 수있는 생성자 매개 변수로 ICommonDiaglogs를 허용합니다. 보너스 : 예상되는 MessageBox 호출에 대해 주장 할 수 있습니다.
Tony O'Hagan

1
@ 토니, 좋은 생각이야. 이것이 가장 좋은 방법입니다. 나는 당시에 내가 무슨 생각을했는지 모르겠다. 나는 그 당시에도 의존성 주입이 나에게 새로운 것이라고 생각합니다.
dan-gph

3
진지하게, 사람들은 의존성 주입에 대해 배우고 이차적으로 모의 객체에 대해 배웁니다. 의존성 주입은 프로그래밍에 혁명을 일으킬 것입니다.
dan-gph

2
UnitTestDetector.IsInUnitTest를 "return true"로 구현하면 단위 테스트가 통과됩니다. ;) 단위 테스트가 불가능 해 보이는 재미있는 것들 중 하나입니다.
Samer Adra

1
Microsoft.VisualStudio.QualityTools.UnitTestFramework가 더 이상 작동하지 않았습니다. Microsoft.VisualStudio.TestPlatform.TestFramework로 변경했습니다. 다시 작동합니다.
Alexander

22

Ryan의 솔루션을 단순화하면 다음 정적 속성을 모든 클래스에 추가 할 수 있습니다.

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

2
dan-gph의 답변과 거의 같습니다 (nunit이 아닌 VS 도구 세트를 찾고 있었지만).
Ryan

18

나는 tallseth와 비슷한 접근 방식을 사용합니다.

캐싱을 포함하도록 쉽게 수정할 수있는 기본 코드입니다. 또 다른 좋은 아이디어는 코드 실행을 피하기 위해 프로젝트 기본 진입 점에 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 getter가 호출 될 때마다 해당 속성을 찾습니다 . 성능에 영향을 줄 수있는 경우가있을 수 있습니다. AssemblyName을 확인하는 것은 한 번만 수행되기 때문에 더 저렴합니다. public setter를 사용한 아이디어는 좋지만이 경우 UnitTestDetector 클래스는 공유 어셈블리에 배치되어야합니다.
Sergey

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()입니다 null,Assembly.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 를 선언하면 쉽게 해결할 수 있습니다 .
Sergey

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

3

이것을 사용하십시오 :

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
        }

릴리스 버전을 출시하는 동안 디버그 버전을 테스트하는 것이 일반적으로 나쁜 생각이라고 생각합니다.
패트릭 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 메서드가 run-time 보다 호출되면 , 그렇지 않으면 단위 테스트 모드에 있습니다.

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

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

0

코드가 Windows Forms 애플리케이션의 기본 (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

내 코드에서 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;
   } 

어셈블리를 검사하는 것보다 쉽고 빠릅니다. TEST 환경에 있는지 확인하려는 Ruby On Rails를 상기시킵니다.


1
나는 당신이 클래스의 행동을 수정하기 위해 테스트 자체에 의존하고 있기 때문에 당신이 여기서 반대표를 받았다고 생각합니다.
Riegardt Steyn 2014 년

1
이것은 여기에있는 다른 모든 답변보다 최악이 아닙니다.
DonO
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.