Determine se o código está sendo executado como parte de um teste de unidade

105

Eu tenho um teste de unidade (nUnit). Muitas camadas abaixo da pilha de chamadas, um método falhará se estiver sendo executado por meio de um teste de unidade.

Idealmente, você usaria algo como simulação para configurar o objeto do qual esse método depende, mas este é um código de terceiros e não posso fazer isso sem muito trabalho.

Não quero configurar métodos específicos do nUnit - há muitos níveis aqui e é uma maneira ruim de fazer o teste de unidade.

Em vez disso, o que eu gostaria de fazer é adicionar algo assim no fundo da pilha de chamadas

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

Então, alguma ideia sobre como escrever IsRunningInUnitTest?

PS: Estou totalmente ciente de que este não é um ótimo design, mas acho que é melhor do que as alternativas.

Ryan
fonte
5
Você não deve testar direta ou indiretamente o código de terceiros em um teste de unidade. Você deve isolar seu método em teste da implementação de terceiros.
Craig Stuntz
14
Sim - eu sei disso - em um mundo de ideias, mas às vezes temos que ser um pouco pragmáticos sobre as coisas, não?
Ryan
9
Voltando ao comentário de Craig - não tenho certeza se isso é verdade. Se meu método depende do comportamento da biblioteca de terceiros de uma determinada maneira, isso não deveria fazer parte do teste? Se o aplicativo de terceiros mudar, quero que meu teste falhe. Se você estiver usando simulações de seu teste em relação a como você acha que o aplicativo de terceiros funciona, não como realmente funciona.
Ryan
2
Ryan, você pode testar suposições sobre o comportamento de terceiros, mas esse é um teste separado. Você precisa testar seu próprio código isoladamente.
Craig Stuntz
2
Eu entendo o que você está dizendo, mas para qualquer coisa, exceto um exemplo trivial, você estaria falando sobre uma grande (enorme) quantidade de trabalho e não há nada para garantir que as suposições de sua verificação em seu teste sejam as mesmas que suas suposições em seus métodos reais . Hmm - debate para uma postagem no blog, eu acho, vou mandar um e-mail para você quando eu tiver minhas idéias juntas.
Ryan

Respostas:

80

Já fiz isso antes - tive que segurar meu nariz enquanto fazia, mas consegui. O pragmatismo vence o dogmatismo todas as vezes. Claro, se não é uma boa maneira você pode refatorar para evitá-lo, isso seria ótimo.

Basicamente, eu tinha uma classe "UnitTestDetector" que verificava se o assembly da estrutura NUnit foi carregado no AppDomain atual. Ele só precisava fazer isso uma vez e, em seguida, armazenar em cache o resultado. Feio, mas simples e eficaz.

Jon Skeet
fonte
alguma amostra sobre UnitTestDetector? e semelhante para MSTest?
Kiquenet
4
@Kiquenet: Acho que apenas usaria AppDomain.GetAssembliese verificaria o assembly relevante - para o MSTest, você precisaria verificar quais assemblies são carregados. Veja a resposta de Ryan como exemplo.
Jon Skeet
Esta não é uma boa abordagem para mim. Estou chamando um método UnitTest de um aplicativo de console e ele pensa que é um aplicativo UnitTest.
Bizhan 01 de
1
@Bizhan: Sugiro que você esteja em uma situação bastante especializada e não deve esperar que respostas mais gerais funcionem. Você pode querer fazer uma nova pergunta com todos os seus requisitos específicos. (Qual é a diferença entre "seu código chamando de um aplicativo de console" e "um executor de teste", por exemplo? Como você gostaria de distinguir entre seu aplicativo de console e qualquer outro executor de teste baseado em console?)
Jon Skeet
74

Pegando a ideia de Jon, isso é o que eu tive -

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

Pule na parte de trás, somos todos meninos grandes o suficiente para reconhecer quando estamos fazendo algo que provavelmente não deveríamos;)

Ryan
fonte
2
+1 boa resposta. No entanto, isso pode ser bastante simplificado, veja abaixo: stackoverflow.com/a/30356080/184528
cdiggins
O projeto específico para o qual eu escrevi isso era (e ainda é!) .NET 2.0, então não linq.
Ryan
Este uso funciona para mim, mas parece que o nome do assembly mudou desde então. Mudei
Kiquenet
Tive que desligar o registro de compilações travis ci, ele congelou tudo
jjxtra
Funciona para mim, eu tenho que contornar bugs do .NET core 3 com navalha que só acontecem em testes de unidade.
jjxtra
62

Adaptado da resposta de Ryan. Este é para a estrutura de teste de unidade MS.

O motivo pelo qual preciso disso é porque mostro uma MessageBox sobre erros. Mas meus testes de unidade também testam o código de tratamento de erros, e não quero que uma MessageBox apareça durante a execução de testes de unidade.

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

E aqui está um teste de unidade para isso:

    [TestMethod]
    public void IsInUnitTest()
    {
        Assert.IsTrue(UnitTestDetector.IsInUnitTest, 
            "Should detect that we are running inside a unit test."); // lol
    }
dan-gph
fonte
8
Eu tenho uma maneira melhor que resolve seu problema MessageBox e anula esse hack e oferece mais casos de teste de unidade. Eu uso uma classe que implementa uma interface que chamo de ICommonDialogs. A classe de implementação exibe todos os diálogos pop-up (MessageBox, diálogos de arquivo, seletor de cores, diálogo de conexão de banco de dados, etc.). As classes que precisam exibir caixas de mensagem aceitam ICommonDiaglogs como um parâmetro de construtor que podemos simular no teste de unidade. Bônus: você pode fazer valer as chamadas esperadas do MessageBox.
Tony O'Hagan
1
@ Tony, boa ideia. Essa é claramente a melhor maneira de fazer isso. Não sei o que estava pensando na hora. Acho que a injeção de dependência ainda era nova para mim naquela época.
dan-gph
3
Sério, pessoal, aprendam sobre injeção de dependência e, secundariamente, objetos fictícios. A injeção de dependência revolucionará sua programação.
dan-gph
2
Implementarei UnitTestDetector.IsInUnitTest como "return true" e seu teste de unidade será aprovado. ;) Uma daquelas coisas engraçadas que parece impossível de testar a unidade.
Samer Adra
1
Microsoft.VisualStudio.QualityTools.UnitTestFramework não funcionou mais para mim. Alterado para Microsoft.VisualStudio.TestPlatform.TestFramework - que funciona novamente.
Alexander,
21

Simplificando a solução de Ryan, você pode simplesmente adicionar a seguinte propriedade estática a qualquer classe:

    public static readonly bool IsRunningFromNUnit = 
        AppDomain.CurrentDomain.GetAssemblies().Any(
            a => a.FullName.ToLowerInvariant().StartsWith("nunit.framework"));
cdiggins
fonte
2
Quase a mesma que a resposta de dan-gph (embora ele estivesse procurando pelo conjunto de ferramentas VS, não por nunit).
Ryan
18

Eu uso uma abordagem semelhante ao Tallseth

Este é o código básico que pode ser facilmente modificado para incluir o cache. Outra boa ideia seria adicionar um setter IsRunningInUnitTeste chamar UnitTestDetector.IsRunningInUnitTest = falseo ponto de entrada principal do seu projeto para evitar a execução do código.

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;
        }
    }
}
Jürgen Steinblock
fonte
Gosto dessa abordagem mais do que das respostas com maior votação. Não acho que seja seguro presumir que os assemblies de teste de unidade só serão carregados durante um teste de unidade e o nome do processo também pode variar de desenvolvedor para desenvolvedor (por exemplo, alguns usam o executor de teste R #).
EM0
Essa abordagem funcionaria, mas procurará esses atributos cada vez que o getter IsRunningInUnitTest for chamado. Pode haver alguns casos em que isso pode afetar o desempenho. Verificar o AssemblyName é mais barato porque é feito apenas uma vez. A ideia com o setter público é boa, mas, neste caso, a classe UnitTestDetector deve ser colocada em um assembly compartilhado.
Sergey de
13

Pode ser útil, verificar o ProcessName atual:

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

E esta função também deve ser verificada por teste de unidade:

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

Referências:
Matthew Watson em http://social.msdn.microsoft.com/Forums/en-US/csharplanguage/thread/11e68468-c95e-4c43-b02b-7045a52b407e/

Kiquenet
fonte
|| processName.StartsWith("testhost") // testhost.x86para VS 2019
Kiquenet
9

Em modo de teste, Assembly.GetEntryAssembly()parece ser null.

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

Observe que se Assembly.GetEntryAssembly()é null, Assembly.GetExecutingAssembly()não é.

A documentação diz: O GetEntryAssemblymétodo pode retornar nullquando um assembly gerenciado foi carregado de um aplicativo não gerenciado.

Eric Bole-Feysot
fonte
8

Em algum lugar do projeto que está sendo testado:

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

Em algum lugar do seu projeto de teste de unidade:

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

Elegante, não. Mas direto e rápido. AssemblyInitializeré para MS Test. Eu esperaria que outras estruturas de teste tivessem equivalentes.

Edward Brey
fonte
1
Se o código que você está testando cria AppDomains adicionais, IsRunningInUnitTestnão é definido como verdadeiro nesses AppDomains.
Edward Brey
Mas pode ser facilmente resolvido adicionando um assembly compartilhado ou declarando IsRunningInUnitTest em cada domínio.
Sergey de
3

Eu uso isso apenas para ignorar a lógica que desativa todos os TraceAppenders em log4net durante a inicialização quando nenhum depurador está conectado. Isso permite que os testes de unidade sejam registrados na janela de resultados do Resharper, mesmo quando executados no modo sem depuração.

O método que usa esta função é chamado na inicialização do aplicativo ou ao iniciar um dispositivo de teste.

É semelhante à postagem de Ryan, mas usa LINQ, elimina o requisito System.Reflection, não armazena em cache o resultado e é privado para evitar o uso incorreto (acidental).

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

Ter uma referência à estrutura nunit não significa que o teste está realmente em execução. Por exemplo, no Unity, quando você ativa os testes do modo de reprodução, as referências da nunidade são adicionadas ao projeto. E quando você executa um jogo, as referências existem, então UnitTestDetector não funcionaria corretamente.

Em vez de verificar o assembly do nunit, podemos pedir ao nunit api para verificar se o código está sendo executado no teste ou não.

using NUnit.Framework;

// ...

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

Editar:

Esteja ciente de que o TestContext pode ser gerado automaticamente se for necessário.

ChessMax
fonte
2
Por favor, não apenas despeje o código aqui. Explique o que faz.
nkr
2

Basta usar isto:

AppDomain.CurrentDomain.IsDefaultAppDomain()

No modo de teste, ele retornará falso.

brilhar
fonte
1

Fiquei infeliz por ter esse problema recentemente. Eu resolvi isso de uma maneira um pouco diferente. Primeiro, eu não estava disposto a supor que o framework nunit nunca seria carregado fora de um ambiente de teste; Eu estava particularmente preocupado com os desenvolvedores executando o aplicativo em suas máquinas. Então, em vez disso, percorri a pilha de chamadas. Em segundo lugar, fui capaz de presumir que o código de teste nunca seria executado em binários de lançamento, portanto, certifiquei-me de que esse código não existisse em um sistema de lançamento.

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
        }
Tallseth
fonte
Acho a ideia de testar a versão de depuração enquanto distribui a versão de lançamento uma má ideia em geral.
Patrick M
1

Funciona como um encanto

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

.

Tom nobre
fonte
1

Os testes de unidade irão ignorar o ponto de entrada do aplicativo. Pelo menos para wpf, winforms e aplicativo de console main()não está sendo chamado.

Se o método principal for chamado, então estamos em tempo de execução , caso contrário, estamos no modo de teste de unidade :

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

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

Considerando que seu código é normalmente executado no thread principal (gui) de um aplicativo do Windows Forms e você deseja que ele se comporte de forma diferente durante a execução de um teste, você pode verificar

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

Estou usando isso para o código que desejo fire and forgot em um aplicativo gui, mas nos testes de unidade, posso precisar do resultado calculado para uma declaração e não quero mexer com vários threads em execução.

Funciona para MSTest. A vantagem é que meu código não precisa verificar a estrutura de teste em si e se eu realmente preciso do comportamento assíncrono em um determinado teste, posso definir meu próprio SynchronizationContext.

Esteja ciente de que este não é um método confiável para, Determine if code is running as part of a unit testconforme solicitado pelo OP, uma vez que o código pode estar sendo executado dentro de um thread, mas para certos cenários, esta pode ser uma boa solução (também: Se eu já estiver executando a partir de um thread de segundo plano, pode não ser necessário para iniciar um novo).

Jürgen Steinblock
fonte
0

Application.Current é nulo quando executado no testador de unidade. Pelo menos para o meu aplicativo WPF usando o testador de unidade MS. É um teste fácil de fazer, se necessário. Além disso, algo para se ter em mente ao usar Application.Current em seu código.

Glauco
fonte
0

Usei o seguinte em VB em meu código para verificar se estamos em um teste de unidade. especificamente, eu não queria que o teste abrisse o Word

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

Que tal usar reflexão e algo assim:

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

O assembly de chamada estará onde seus casos de teste estão e apenas substituirá o MainForm por algum tipo que está em seu código sendo testado.

Jon
fonte
-3

Também há uma solução muito simples quando você está testando uma classe ...

Basta fornecer à classe que você está testando uma propriedade como esta:

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

Agora seu teste de unidade pode definir o booleano "thisIsUnitTest" como verdadeiro, portanto, no código que você deseja pular, adicione:

   if (thisIsUnitTest)
   {
       return;
   } 

É mais fácil e rápido do que inspecionar as montagens. Lembra-me do Ruby On Rails, onde você olha para ver se está no ambiente de TESTE.

Danmar Herholdt
fonte
1
Acho que você foi rejeitado aqui porque está contando com o próprio teste para modificar o comportamento da classe.
Riegardt Steyn
1
Isso não é pior do que todas as outras respostas aqui.
DonO