Визначте, чи працює код як частина одиничного тесту


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

14
Так - я розумію, що - у світі ідеї, але іноді ми маємо бути трохи прагматичними щодо речей ні?
Райан

9
Повертаючись до коментаря Крейга - не впевнений, що це правда. Якщо мій метод спирається на те, що третя сторона бібліотеки поводиться певним чином, чи не повинно це бути частиною тесту? Якщо додаток стороннього виробника зміниться, я хочу, щоб мій тест не вийшов. Якщо ви використовуєте знущання над тестуванням щодо того, як ви думаєте, що працює програма сторонніх розробників, а не як це відбувається насправді.
Райан

2
Раян, ти можеш перевірити припущення про поведінку сторонніх осіб, але це окремий тест. Потрібно протестувати власний код ізольовано.
Craig Stuntz

2
Я розумію, що ви говорите, але, окрім тривіального прикладу, ви б говорили про велику (величезну) кількість роботи, і немає нічого, що гарантує, що припущення, які ви перевіряєте у своєму тесті, такі ж, як і ваші припущення у ваших фактичних методах . Хм - дебати щодо публікації в блозі. Я думаю, я надішлю вам електронний лист, коли зійду свої думки.
Райан

Відповіді:


80

Я робив це раніше - мені довелося затримати ніс, поки я це робив, але я це зробив. Прагматизм щоразу перемагає догматизм. Звичайно, якщо є приємний спосіб, коли ти зможеш цього уникнути, це було б чудово.

В основному у мене був клас "UnitTestDetector", який перевіряв, чи було завантажено структуру збірки NUnit у поточному AppDomain. Це потрібно було зробити лише один раз, а потім кешувати результат. Некрасивий, але простий і ефективний.


будь-який зразок про UnitTestDetector? і схожі на MSTest?
Кікенет

4
@Kiquenet: Я думаю, я б просто скористався AppDomain.GetAssembliesі перевірив відповідну збірку - для MSTest вам потрібно буде переглянути, які збірки завантажуються. Подивіться на приклад відповіді Райана.
Джон Скіт

Для мене це не гарний підхід. Я викликаю метод UnitTest з програми Console, і він вважає, що це додаток UnitTest.
Біжан

1
@Bizhan: Я б припустив, що ти знаходишся в досить спеціалізованій ситуації, і ти не повинен очікувати більш загальних відповідей на роботу. Ви можете поставити нове запитання з усіма вашими конкретними вимогами. (Яка різниця між, наприклад, "вашим кодом, який викликає консольну програму" та "тестовим бігуном"? Як ви хочете розрізняти консольну програму від будь-якого іншого тестового бігуна на основі консолі?)
Джон Скіт

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, тому немає ніяких посилань.
Райан

Це використання для мене працює, але, здається, назва збірки змінилася з тих пір. Я перейшов до рішення
Кікенета

Мені довелося вимкнути ведення журналу для складання
travis

Працює для мене, я повинен зламати навколо .NET core 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
У мене є кращий спосіб вирішити вашу проблему MessageBox і анулює цей злом і доставить більше тестових примірників. Я використовую клас, який реалізує інтерфейс, який я називаю ICommonDialogs. Клас реалізації відображає всі спливаючі діалоги (MessageBox, діалогові вікна файлів, інструмент вибору кольорів, діалогове вікно підключення до бази даних тощо). Класи, яким потрібно відобразити вікна повідомлень, приймають ICommonDiaglogs як параметр конструктора, який ми можемо потім знущатись в одиничному тесті. Бонус: Ви можете стверджувати про очікувані дзвінки MessageBox.
Тоні О'Хаган

1
@ Тоні, гарна ідея. Очевидно, це найкращий спосіб зробити це. Я не знаю, що я тоді думав. Я думаю, що ін'єкція залежності була для мене ще новою.
dan-gph

3
Серйозно, люди дізнаються про ін'єкцію залежності та, по-друге, знущаються над предметами. Введення залежності залежить від вашого програмування.
дан-gph

2
Я буду реалізовувати UnitTestDetector.IsInUnitTest як "повернути істину", і ваш блок перевірки пройде. ;) Один із тих кумедних речей, який здається неможливим одиничним тестом.
Самер Адра

1
Microsoft.VisualStudio.QualityTools.UnitTestFramework більше не працював для мене. Змінено його на Microsoft.VisualStudio.TestPlatform.TestFramework - це працює знову.
Олександр

22

Спростивши рішення Райана, ви можете просто додати наступне статичне властивість до будь-якого класу:

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

2
Приблизно те саме, що відповідь dan-gph (хоча це було шукати набір інструментів VS, а не nunit).
Райан

18

Я використовую аналогічний підхід, як талсіт

Це основний код, який можна легко змінити, щоб включити кешування. Ще однією гарною ідеєю було б додати сетер 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 дешевша, оскільки робиться лише один раз. Ідея з громадським сетером хороша, але в цьому випадку клас 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/


|| processName.StartsWith("testhost") // testhost.x86для VS 2019
Кікенет

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

Елегантний, ні. Але прямо і швидко. AssemblyInitializerпризначений для тесту на MS. Я б очікував, що інші тестові рамки мають еквіваленти.


1
Якщо код, який ви тестуєте, створює додаткові додатки AppDomains, IsRunningInUnitTestне встановлюється істинним у цих AppDomains.
Едвард Брей

Але це можна легко вирішити, додавши спільну збірку або оголосивши IsRunningInUnitTest у кожному домені.
Сергій

3

Я використовую це лише для пропускання логіки, яка відключає всі TraceAppenders в log4net під час запуску, коли не встановлено налагоджувача. Це дозволяє одиничним тестам увійти до вікна результатів Resharper навіть під час роботи в режимі, що не налагоджує.

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

Він подібний до публікації Райана, але використовує LINQ, скасовує вимогу System.Reflection, не кешує результат і є приватним для запобігання (випадкового) використання.

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

3

Те, що посилання на рамку nunit не означає, що тест насправді працює. Наприклад, в Unity, коли ви активуєте тести режиму відтворення, до проекту додаються посилання на нуніти. І коли ви запускаєте гру, посилання існують, тож 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()

У тестовому режимі воно повернеться хибним.


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
        }

Я вважаю, що ідея тестування версії налагодження під час доставки версії є поганою ідеєю.
Патрік М

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()не викликається.

Якщо основний метод викликається, ніж ми в режимі виконання , інакше ми перебуваємо в тестовому режимі:

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

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

0

Зважаючи на те, що ваш код, як правило, запустіть у головному (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. Перевага в тому, що моєму коду не потрібно перевіряти наявність тестуючої рамки, і якщо мені справді потрібна поведінка асинхронізації в певному тесті, я можу встановити власний SynchronizationContext.

Майте на увазі, що це не надійний метод, Determine if code is running as part of a unit testяк вимагає ОП, оскільки код може працювати всередині потоку, але для певних сценаріїв це може бути гарним рішенням (також: Якщо я вже запущений з фонової нитки, це може не знадобитися для початку нового).


0

Application.Current є нульовим при запуску під тестером одиниць. Принаймні для мого додатка WPF, що використовує тестер MS Unit. Це простий тест, який потрібно скласти за потреби. Крім того, про що слід пам’ятати при використанні 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" на істинне, тому в коді, який потрібно пропустити, додайте:

   if (thisIsUnitTest)
   {
       return;
   } 

Це простіше і швидше, ніж огляд збірок. Нагадує мені про Ruby On Rails, де ви б хотіли подивитися, чи перебуваєте ви в середовищі TEST.


1
Я думаю, що ви тут були голосовані, тому що ви покладаєтесь на тест, щоб змінити поведінку класу.
Riegardt Steyn

1
Це не гірше, ніж усі інші відповіді.
ДонО
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.