Bir birim testinin parçası olarak kodun çalışıp çalışmadığını belirleyin


107

Bir birim testim var (nUnit). Çağrı yığınının altındaki birçok katman, bir birim testi aracılığıyla çalışıyorsa bir yöntem başarısız olur.

İdeal olarak, bu yöntemin bağlı olduğu nesneyi kurmak için alay gibi bir şey kullanırsınız, ancak bu 3. taraf kodudur ve bunu çok fazla çalışma yapmadan yapamam.

Kurulum nUnit'e özgü yöntemler istemiyorum - burada çok fazla düzey var ve birim testi yapmanın kötü bir yolu.

Bunun yerine yapmak istediğim şey, çağrı yığınının derinliklerine böyle bir şey eklemek

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

Peki, IsRunningInUnitTest'in nasıl yazılacağı hakkında herhangi bir fikriniz var mı?

Not: Bunun harika bir tasarım olmadığının tamamen farkındayım, ancak alternatiflerinden daha iyi olduğunu düşünüyorum .


5
Bir birim testinde üçüncü taraf kodunu doğrudan veya dolaylı olarak test etmemelisiniz. Test edilen yönteminizi üçüncü taraf uygulamasından ayırmalısınız.
Craig Stuntz

15
Evet - bunun farkındayım - bir fikir dünyasında, ama bazen bazı şeyler hakkında biraz pragmatik olmamız gerekir mi?
Ryan

9
Craig'in yorumuna geri dönersek - bunun doğru olduğundan emin değilim. Yöntemim, üçüncü taraf kitaplığının belirli bir şekilde davranmasına dayanıyorsa, bu, testin bir parçası olmamalı mı? 3. taraf uygulaması değişirse testimin başarısız olmasını istiyorum. Kullanıyorsanız, testinizi 3. taraf uygulamasının gerçekte nasıl çalıştığını değil, nasıl çalıştığını düşündüğünüzle alay eder.
Ryan

2
Ryan, üçüncü taraf davranışları hakkındaki varsayımları test edebilirsin, ama bu ayrı bir test. Kendi kodunuzu izole bir şekilde test etmeniz gerekir.
Craig Stuntz

2
Söylediklerinizi anlıyorum ama önemsiz bir örnek dışında herhangi bir şey için büyük (çok büyük) bir işten bahsediyorsunuzdur ve testinizde kontrol ettiğiniz varsayımların gerçek yöntemlerinizdeki varsayımlarınızla aynı olmasını sağlayacak hiçbir şey yoktur. . Hmm - bir blog yazısı için tartışma Sanırım, düşüncelerimi bir araya topladığımda size bir e-posta göndereceğim.
Ryan

Yanıtlar:


80

Bunu daha önce yaptım - bunu yaparken burnumu tutmam gerekiyordu ama yaptım. Pragmatizm her seferinde dogmatizmi yener. Orada değilse, doğal olan bunu önlemek için planı ayrı güzel bir yol, bu harika olurdu.

Temel olarak, NUnit çerçeve derlemesinin geçerli AppDomain'e yüklenip yüklenmediğini kontrol eden bir "UnitTestDetector" sınıfım vardı. Bunu yalnızca bir kez yapması ve ardından sonucu önbelleğe alması gerekiyordu. Çirkin ama basit ve etkili.


UnitTestDetector hakkında herhangi bir örnek? ve MSTest için benzer?
Kiquenet

4
@Kiquenet: Sanırım sadece AppDomain.GetAssembliesilgili montajı kullanıp kontrol ederdim - MSTest için hangi montajların yüklendiğine bakmanız gerekir. Örnek için Ryan'ın cevabına bakın.
Jon Skeet

Bu benim için iyi bir yaklaşım değil. Bir Konsol Uygulamasından UnitTest yöntemini arıyorum ve bunun bir UnitTest Uygulaması olduğunu düşünüyor.
Bizhan

1
@Bizhan: O zaman oldukça özel bir durumda olduğunuzu ve işe yarayacak daha genel cevaplar beklememenizi öneririm. Tüm özel gereksinimlerinizi içeren yeni bir soru sormak isteyebilirsiniz. (Örneğin, "bir konsol uygulamasından gelen kodunuz" ile "bir test çalıştırıcısı" arasındaki fark nedir? Konsol uygulamanız ile diğer konsol tabanlı test çalıştırıcısı arasında nasıl bir ayrım yapmak istersiniz?)
Jon Skeet

75

Jon'un fikrinden yola çıkarak bulduğum şey bu -

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

Arkada durun, hepimiz muhtemelen yapmamamız gereken bir şeyi yaptığımızda fark edecek kadar büyük çocuklarız;)


2
+1 Güzel cevap. Yine de bu biraz basitleştirilebilir, aşağıya bakın: stackoverflow.com/a/30356080/184528
cdiggins

Bunu için yazdığım belirli proje (ve hala!) .NET 2.0 idi, bu yüzden linq yok.
Ryan,

Bu benim için çalışıyordu ama o zamandan beri montaj adı değişmiş gibi görünüyor. Ben geçiş Kiquenet çözümüyle
The_Black_Smurf

Travis ci yapıları için günlük kaydını kapatmak zorunda kaldım, her şeyi dondurdu
jjxtra

Benim için işe yarıyor, .NET core 3 hatalarını yalnızca birim testlerinde oluşan ustura ile hacklemem gerekiyor.
jjxtra

62

Ryan'ın cevabından uyarlanmıştır. Bu, MS birim test çerçevesi içindir.

Buna ihtiyacımın nedeni, hatalarda bir MessageBox göstermemdir. Ancak birim testlerim aynı zamanda hata işleme kodunu da test ediyor ve birim testlerini çalıştırırken bir MessageBox'ın açılmasını istemiyorum.

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

Ve işte bunun için bir birim testi:

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

8
MessageBox sorununuzu çözen ve bu kesmeyi geçersiz kılan ve daha fazla birim test durumu sunan daha iyi bir yolum var. ICommonDialogs adını verdiğim bir arabirimi uygulayan bir sınıf kullanıyorum. Uygulama sınıfı, tüm açılır iletişim kutularını görüntüler (Mesaj Kutusu, Dosya iletişim kutuları, Renk seçici, veritabanı bağlantı iletişim kutusu vb.). Mesaj kutularını görüntülemesi gereken sınıflar, ICommonDiaglogs'u daha sonra birim testinde alay edebileceğimiz bir yapıcı parametre olarak kabul eder. Bonus: Beklenen MessageBox çağrılarını iddia edebilirsiniz.
Tony O'Hagan

1
@Tony, iyi fikir. Açıkça bunu yapmanın en iyi yolu bu. O sırada ne düşündüğümü bilmiyorum. O zamanlar bağımlılık enjeksiyonu benim için hala yeni olduğunu düşünüyorum.
dan-gph

3
Cidden, insanlar, bağımlılık enjeksiyonu hakkında bilgi edinin ve ikincil olarak, nesnelerle alay edin. Bağımlılık ekleme, programlamanızda devrim yaratacak.
dan-gph

2
UnitTestDetector.IsInUnitTest'i "return true" olarak uygulayacağım ve birim testiniz geçecek. ;) Birim testi yapmak imkansız görünen o komik şeylerden biri.
Samer Adra

1
Microsoft.VisualStudio.QualityTools.UnitTestFramework artık benim için çalışmadı. Tekrar çalışan Microsoft.VisualStudio.TestPlatform.TestFramework olarak değiştirildi.
Alexander

22

Ryan'ın çözümünü basitleştirerek, aşağıdaki statik özelliği herhangi bir sınıfa ekleyebilirsiniz:

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

2
Dan-gph'nin cevabıyla hemen hemen aynı (ancak bu, nunit değil VS araç setini arıyordu).
Ryan,

19

Tallseth ile benzer bir yaklaşım kullanıyorum

Bu, önbelleğe almayı içerecek şekilde kolayca değiştirilebilen temel koddur. Başka bir iyi fikir , kodun yürütülmesini önlemek için bir belirleyici eklemek IsRunningInUnitTestve UnitTestDetector.IsRunningInUnitTest = falseprojelerinizin ana giriş noktasını aramaktır.

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

Bu yaklaşımı yüksek oy alan cevaplardan daha çok seviyorum. Birim testi düzeneklerinin yalnızca bir birim testi sırasında yükleneceğini ve işlem adının da geliştiriciden geliştiriciye değişebileceğini varsaymanın güvenli olduğunu sanmıyorum (örneğin, bazıları R # test çalıştırıcısını kullanır).
EM0

Bu yaklaşım işe yarayacaktır, ancak IsRunningInUnitTest alıcı her çağrıldığında bu öznitelikleri arayacaktır . Performansı etkileyebileceği bazı durumlar olabilir. AssemblyName'i kontrol etmek daha ucuzdur çünkü yalnızca bir kez yapılır. Ortak ayarlayıcı ile fikir iyidir, ancak bu durumda UnitTestDetector sınıfı paylaşılan bir derlemeye yerleştirilmelidir.
Sergey

13

Mevcut ProcessName'i kontrol etmek faydalı olabilir:

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

Ve bu fonksiyon aynı zamanda unittest ile kontrol edilmelidir:

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

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


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

9

Test modunda Assembly.GetEntryAssembly()görünüyor null.

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

Eğer unutmayın Assembly.GetEntryAssembly()olduğunu null,Assembly.GetExecutingAssembly() değil.

Dokümantasyon diyor ki: GetEntryAssemblyyöntemi dönebilirsiniz nullmontaj yönetilen bir yönetilmeyen bir uygulamadan yüklendiğinde.


8

Test edilen projede bir yerde:

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

Birim testi projenizde bir yerde:

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

Zarif, hayır. Ama anlaşılır ve hızlı. AssemblyInitializerMS Testi içindir. Diğer test çerçevelerinin eşdeğerleri olmasını beklerdim.


1
Test ettiğiniz kod ek AppDomains oluşturursa IsRunningInUnitTest, bu AppDomains'de true olarak ayarlanamaz.
Edward Brey

Ancak, paylaşılan bir derleme eklenerek veya her etki alanında IsRunningInUnitTest bildirilerek kolayca çözülebilir .
Sergey

3

Bunu kullanmak sadece , hata ayıklayıcı eklenmediğinde başlatma sırasında log4net'teki tüm TraceAppenders'ı devre dışı bırakan mantığı atlamak için . Bu, birim testlerinin hata ayıklama olmayan modda çalışırken bile Yeniden Paylaşım sonuçları penceresinde günlüğe kaydetmesine olanak tanır.

Bu işlevi kullanan yöntem, uygulamanın başlangıcında veya bir test fikstürüne başlarken çağrılır.

Ryan'ın gönderisine benzer, ancak LINQ kullanır, System.Reflection gereksinimini kaldırır, sonucu önbelleğe almaz ve (kazara) kötüye kullanımı önlemek için özeldir.

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

3

Nunit çerçevesine referans olması, testin gerçekten çalıştığı anlamına gelmez. Örneğin Unity'de oynatma modunu etkinleştirdiğinizde, nunit referansları projeye eklenir. Ve bir oyunu çalıştırdığınızda referanslar mevcuttur, bu nedenle UnitTestDetector düzgün çalışmayacaktır.

Nunit derlemesini kontrol etmek yerine, nunit api'den kodun test altında olup olmadığını kontrol etmesini isteyebiliriz.

using NUnit.Framework;

// ...

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

Düzenle:

Gerekirse TestContext'in otomatik olarak oluşturulabileceğine dikkat edin .


2
Lütfen sadece kodu buraya dökmeyin. Ne yaptığını açıklayın.
nkr

3

Sadece şunu kullan:

AppDomain.CurrentDomain.IsDefaultAppDomain()

Test modunda yanlış döndürür.


1

Son zamanlarda bu sorunu yaşadığım için mutsuzdum. Ben biraz farklı bir şekilde çözdüm. Birincisi, nunit çerçevesinin asla bir test ortamı dışında yüklenmeyeceğini varsaymak konusunda isteksizdim; Uygulamayı makinelerinde çalıştıran geliştiriciler konusunda özellikle endişeliydim. Bunun yerine çağrı yığınını yürüdüm. İkincisi, test kodunun hiçbir zaman sürüm ikili dosyalarına karşı çalıştırılmayacağı varsayımını yapabildim, bu yüzden bu kodun bir sürüm sisteminde mevcut olmadığından emin oldum.

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
        }

Yayın sürümünü gönderirken hata ayıklama sürümünü test etme fikrini genel olarak kötü bir fikir olarak görüyorum.
Patrick M

1

tıkır tıkır çalışıyor

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

Birim testleri, uygulama giriş noktasını atlayacak. En azından wpf için winforms ve konsol uygulaması main()çağrılmıyor.

Ana yöntem, çalışma zamanında olduğundan daha çağrılırsa , aksi takdirde birim test modundayız:

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

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

0

Kodunuzun normal olarak bir Windows form uygulamasının ana (gui) iş parçacığında çalıştığını ve kontrol edebileceğiniz bir testte çalışırken farklı davranmasını istediğinizi düşünürsek

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

Bunu fire and forgotbir gui uygulamasında istediğim kod için kullanıyorum, ancak birim testlerinde bir iddia için hesaplanan sonuca ihtiyacım olabilir ve çalışan birden çok iş parçacığı ile uğraşmak istemiyorum.

MSTest için çalışıyor. Bunun avantajı, kodumun test çerçevesini kontrol etmesine gerek olmaması ve belirli bir testte eşzamansız davranışa gerçekten ihtiyacım olursa kendi SynchronizationContext'imi ayarlayabilirim.

Determine if code is running as part of a unit testKod bir iş parçacığı içinde çalışabileceğinden, bunun OP tarafından talep edilen güvenilir bir yöntem olmadığını unutmayın , ancak bazı senaryolar için bu iyi bir çözüm olabilir (ayrıca: Zaten bir arka plan iş parçacığından çalıştırıyorsam, gerekli olmayabilir. yenisini başlatmak için).


0

Uygulama. Birim test cihazı altında çalışırken akım boş. En azından MS Unit tester kullanan WPF uygulamam için. Gerekirse bu, yapılması kolay bir testtir. Ayrıca, Kodunuzda Application.Current kullanırken akılda tutulması gereken bir şey.


0

Bir birim testinde olup olmadığımızı kontrol etmek için kodumda aşağıdakileri VB'de kullandım. özellikle testin Word'ü açmasını istemedim

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

0

Düşünmeyi ve bunun gibi bir şeyi kullanmaya ne dersiniz:

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

Çağıran derleme, test durumlarınızın olduğu yerde olacak ve test edilmekte olan kodunuzda bulunan bir tür MainForm'un yerine geçecektir.


-3

Bir sınıfı test ederken de gerçekten basit bir çözüm var ...

Test ettiğiniz sınıfa şöyle bir özelliği verin:

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

Artık birim testiniz "thisIsUnitTest" boole değerini true olarak ayarlayabilir, bu nedenle atlamak istediğiniz kodda şunu ekleyin:

   if (thisIsUnitTest)
   {
       return;
   } 

Montajları incelemekten daha kolay ve hızlıdır. TEST ortamında olup olmadığınızı görmek için bakacağınız Ruby On Rails'i hatırlatıyor.


1
Sanırım burada aşağı oylandınız çünkü sınıfın davranışını değiştirmek için testin kendisine güveniyorsunuz.
Riegardt Steyn

1
Bu, buradaki diğer tüm cevaplardan daha kötü değil.
DonO
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.