Определите, работает ли код как часть unit test

У меня есть unit test (nUnit). Многие слои вниз по стеку вызовов не будут работать, если он запущен через unit test.

В идеале вы использовали бы что-то вроде насмешки над установкой объекта, в котором этот метод зависит, но это сторонний код, и я не могу этого сделать без большой работы.

Я не хочу устанавливать nUnit конкретные методы - здесь слишком много уровней, и это плохой способ сделать unit test.

Вместо того, что я хотел бы сделать, это добавить что-то вроде этого глубоко в стек вызовов

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

Итак, любые идеи о том, как писать IsRunningInUnitTest?

P.S. Я полностью осознаю, что это не большой дизайн, но я считаю его лучше, чем альтернативы.

Ответ 1

Я сделал это раньше - мне нужно было держать нос, пока я это делал, но я сделал это. Прагматизм побеждает догматизм каждый раз. Конечно, если есть хороший способ, который вы можете реорганизовать, чтобы избежать этого, это было бы здорово.

В основном у меня был класс "UnitTestDetector", который проверял, была ли загружена сборка инфраструктуры NUnit в текущем AppDomain. Это нужно сделать только один раз, а затем кешировать результат. Уродливый, но простой и эффективный.

Ответ 2

Принимая идею Джона, это то, с чем я столкнулся -

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

Потяните вниз по спине, мы все достаточно мальчики, чтобы узнать, когда мы делаем что-то, чего мы, вероятно, не должны;)

Ответ 3

Адаптировано из ответа Райана. Это для рамки MS unit test.

Я нуждаюсь в этом, потому что я показываю 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; }
}

И здесь unit test для него:

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

Ответ 4

Я использую такой же подход, как 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;
        }
    }
}

Ответ 5

Упрощая решение Ryan, вы можете просто добавить следующее статическое свойство в любой класс:

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

Ответ 6

В тестовом режиме 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, когда управляемая сборка была загружена из неуправляемого приложения.

Ответ 7

Может быть полезно, проверяя текущее Имя процесса:

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/

Ответ 8

Где-то в тестируемом проекте:

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

Где-то в вашем проекте unit test:

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

Элегантный, нет. Но прямо и быстро. AssemblyInitializer - для теста MS. Я ожидал бы, что другие тестовые среды будут иметь эквиваленты.

Ответ 9

Я использую это только для пропущенной логики, которая отключает все TraceAppenders в log4net во время запуска, если отладчик не подключен. Это позволяет модульным тестам регистрироваться в окне результатов Resharper даже при работе в режиме без отладки.

Метод, который использует эту функцию, либо вызывается при запуске приложения, либо при запуске тестового прибора.

Он похож на пост Ryan, но использует LINQ, отбрасывает требование System.Reflection, не кэширует результат и является конфиденциальным для предотвращения (случайного) неправильного использования.

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

Ответ 10

Я был недоволен этой проблемой в последнее время. Я решил это несколько иначе. Во-первых, я не хотел делать предположение, что структура 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
        }

Ответ 11

работает как шарм

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

.

Ответ 12

Учитывая, что ваш код нормально запускается в основном (gui) потоке приложения форм Windows и вы хотите, чтобы он работал во время работы в тесте, вы можете проверить

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 forgot в приложении gui, но в модульных тестах мне может понадобиться вычисленный результат для утверждения, и я не хочу связываться с несколькими потоками.

Работает для MSTest. Преимущество этого в том, что моему коду не нужно проверять сами рамки тестирования, и если мне действительно нужно поведение async в определенном тесте, я могу установить свой собственный SynchronizationContext.

Имейте в виду, что это не надежный метод Determine if code is running as part of a unit test по запросу OP, поскольку код может работать внутри потока, но для определенных сценариев это может быть хорошим решением (также: если я уже запускаюсь из фонового потока, возможно, нет необходимости запускать новый).

Ответ 13

Наличие ссылки на инфраструктуру nunit не означает, что тест действительно запущен. Например, в Unity, когда вы активируете тесты режима воспроизведения, в проект добавляются ссылки nunit. И когда вы запускаете игру, ссылки существуют, поэтому UnitTestDetector работает неправильно.

Вместо проверки сборки nunit мы можем попросить nunit api проверить код при выполнении теста сейчас или нет.

using NUnit.Framework;

// ...

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

Ответ 14

Application.Current имеет значение null при работе под тестером устройства. По крайней мере, для моего приложения WPF, использующего тестер MS Unit. Это легкий тест, если нужно. Кроме того, что-то нужно иметь в виду при использовании Application.Current в вашем коде.

Ответ 15

Я использовал следующее в VB в своем коде, чтобы проверить, участвуем ли мы в модульном тесте. spifically я не хотел, чтобы тест открыл Word

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

Ответ 16

Существует очень простое решение, когда вы тестируете класс...

Просто дайте классу, который вы тестируете, например:

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

Теперь ваш unit test может установить логическое значение thisIsUnitTest в значение true, поэтому в коде, который вы хотите пропустить, добавьте:

   if (thisIsUnitTest)
   {
       return;
   } 

Это проще и быстрее, чем проверка сборок. Напоминает мне о Ruby On Rails, где вы бы посмотрели, находитесь ли вы в среде TEST.