Tentukan apakah kode dijalankan sebagai bagian dari pengujian unit


105

Saya memiliki tes unit (nUnit). Banyak lapisan di bawah tumpukan panggilan, sebuah metode akan gagal jika dijalankan melalui pengujian unit.

Idealnya Anda akan menggunakan sesuatu seperti mengejek untuk mengatur objek yang bergantung pada metode ini tetapi ini adalah kode pihak ke-3 dan saya tidak dapat melakukannya tanpa banyak pekerjaan.

Saya tidak ingin mengatur metode khusus nUnit - ada terlalu banyak level di sini dan cara yang buruk dalam melakukan pengujian unit.

Sebaliknya apa yang ingin saya lakukan adalah menambahkan sesuatu seperti ini jauh di dalam tumpukan panggilan

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

Jadi ada ide tentang cara menulis IsRunningInUnitTest?

PS Saya sepenuhnya sadar bahwa ini bukan desain yang bagus, tapi saya pikir ini lebih baik daripada alternatifnya.


5
Anda tidak boleh secara langsung atau tidak langsung menguji kode pihak ketiga dalam pengujian unit. Anda harus memisahkan metode yang sedang diuji dari penerapan pihak ketiga.
Craig Stuntz

14
Ya - saya menyadarinya - di dunia ide, tetapi terkadang kita harus sedikit pragmatis tentang hal-hal, bukan?
Ryan

9
Kembali ke komentar Craig - tidak yakin itu benar. Jika metode saya bergantung pada pustaka pihak ketiga yang berperilaku dengan cara tertentu, bukankah seharusnya ini menjadi bagian dari pengujian? Jika aplikasi pihak ke-3 berubah, saya ingin pengujian saya gagal. Jika Anda mengolok-olok pengujian Anda terhadap cara kerja aplikasi pihak ketiga, bukan cara kerjanya yang sebenarnya.
Ryan

2
Ryan, Anda bisa menguji asumsi tentang perilaku pihak ketiga, tapi itu tes terpisah. Anda perlu menguji kode Anda sendiri secara terpisah.
Craig Stuntz

2
Saya mengerti apa yang Anda katakan tetapi untuk apa pun kecuali contoh sepele Anda akan berbicara tentang sejumlah besar (besar) pekerjaan dan tidak ada yang memastikan bahwa asumsi pemeriksaan Anda dalam pengujian Anda sama dengan asumsi Anda dalam metode aktual Anda . Hmm - debat untuk posting blog Saya pikir, saya akan mengirimi Anda email ketika saya sudah mengerti.
Ryan

Jawaban:


80

Saya pernah melakukan ini sebelumnya - saya harus menahan hidung saat melakukannya, tetapi saya berhasil. Pragmatisme mengalahkan dogmatisme setiap saat. Tentu saja, jika ada adalah cara yang baik Anda dapat refactor untuk menghindarinya, yang akan menjadi besar.

Pada dasarnya saya memiliki kelas "UnitTestDetector" yang memeriksa apakah rakitan kerangka NUnit dimuat di AppDomain saat ini. Ini hanya perlu melakukan ini sekali, lalu cache hasilnya. Jelek, tapi sederhana dan efektif.


ada contoh tentang UnitTestDetector? dan serupa untuk MSTest?
Kiquenet

4
@Kiquenet: Saya pikir saya hanya akan menggunakan AppDomain.GetAssembliesdan memeriksa rakitan yang relevan - untuk MSTest Anda perlu melihat rakitan mana yang dimuat. Lihat jawaban Ryan sebagai contoh.
Jon Skeet

Ini bukan pendekatan yang baik untuk saya. Saya memanggil metode UnitTest dari Aplikasi Konsol dan menganggapnya sebagai Aplikasi UnitTest.
Bizhan

1
@ Bizhan: Saya menyarankan Anda berada dalam situasi yang agak terspesialisasi, dan Anda seharusnya tidak mengharapkan jawaban yang lebih umum berfungsi. Anda mungkin ingin mengajukan pertanyaan baru dengan semua persyaratan khusus Anda. (Apa perbedaan antara "kode yang memanggil dari aplikasi konsol" dan "pelari pengujian" misalnya? Bagaimana Anda ingin membedakan antara aplikasi konsol Anda dan runner pengujian berbasis konsol lainnya?)
Jon Skeet

74

Mengambil ide Jon, inilah yang saya pikirkan -

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

Pipa di belakang kita semua anak laki-laki cukup besar untuk mengenali ketika kita melakukan sesuatu yang mungkin tidak seharusnya kita lakukan;)


2
+1 Jawaban yang bagus. Ini bisa sedikit disederhanakan, lihat di bawah: stackoverflow.com/a/30356080/184528
cdiggins

Proyek khusus yang saya tulis ini untuk (dan masih!). NET 2.0 jadi tidak ada LINQ.
Ryan

Ini digunakan untuk bekerja untuk saya tetapi sepertinya nama assembly telah berubah sejak saat itu. Saya beralih ke solusi Kiquenet ini
The_Black_Smurf

Saya harus mematikan logging untuk travis ci build, semuanya membekukan semuanya
jjxtra

Berfungsi untuk saya, saya harus meretas bug .NET core 3 dengan pisau cukur yang hanya terjadi dalam pengujian unit.
jjxtra

62

Diadaptasi dari jawaban Ryan. Yang ini untuk kerangka pengujian unit MS.

Alasan saya membutuhkan ini adalah karena saya menampilkan MessageBox pada kesalahan. Tetapi pengujian unit saya juga menguji kode penanganan kesalahan, dan saya tidak ingin MessageBox muncul saat menjalankan pengujian unit.

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

Dan inilah unit test untuk itu:

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

8
Saya memiliki cara yang lebih baik untuk memecahkan masalah MessageBox Anda dan membatalkan peretasan ini dan memberikan lebih banyak kasus uji unit. Saya menggunakan kelas yang mengimplementasikan antarmuka yang saya sebut ICommonDialogs. Kelas implementasi menampilkan semua dialog pop up (MessageBox, dialog File, Color picker, dialog koneksi database, dll). Kelas yang perlu menampilkan kotak pesan menerima ICommonDiaglogs sebagai parameter konstruktor yang kemudian dapat kita tiru dalam pengujian unit. Bonus: Anda dapat menegaskan panggilan MessageBox yang diharapkan.
Tony O'Hagan

1
@Tony, ide bagus. Itu jelas cara terbaik untuk melakukannya. Saya tidak tahu apa yang saya pikirkan saat itu. Menurut saya injeksi ketergantungan masih baru bagi saya saat itu.
dan-gph

3
Serius, orang, belajar tentang injeksi ketergantungan, dan yang kedua, objek tiruan. Injeksi ketergantungan akan merevolusi pemrograman Anda.
dan-gph

2
Saya akan menerapkan UnitTestDetector.IsInUnitTest sebagai "return true" dan pengujian unit Anda akan lulus. ;) Salah satu hal lucu yang tampaknya mustahil untuk diuji unit.
Samer Adra

1
Microsoft.VisualStudio.QualityTools.UnitTestFramework tidak berfungsi lagi untuk saya. Mengubahnya menjadi Microsoft.VisualStudio.TestPlatform.TestFramework - yang berfungsi kembali.
Alexander

22

Menyederhanakan solusi Ryan, Anda cukup menambahkan properti statis berikut ke kelas mana pun:

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

2
Hampir sama dengan jawaban dan-gph (meskipun itu mencari perangkat VS, bukan nunit).
Ryan

18

Saya menggunakan pendekatan yang mirip dengan tallseth

Ini adalah kode dasar yang dapat dengan mudah dimodifikasi untuk menyertakan caching. Ide bagus lainnya adalah menambahkan setter ke IsRunningInUnitTestdan memanggil UnitTestDetector.IsRunningInUnitTest = falsetitik masuk utama proyek Anda untuk menghindari eksekusi kode.

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

Saya lebih menyukai pendekatan ini daripada jawaban yang dipilih lebih tinggi. Saya rasa tidak aman untuk mengasumsikan bahwa rakitan pengujian unit hanya akan dimuat selama pengujian unit dan nama proses juga dapat bervariasi dari pengembang ke pengembang (mis. Beberapa menggunakan runner uji R #).
EM0

Pendekatan ini akan berhasil tetapi akan mencari atribut tersebut setiap kali pengambil IsRunningInUnitTest dipanggil. Mungkin ada beberapa kasus yang dapat memengaruhi kinerja. Memeriksa AssemblyName lebih murah karena dilakukan hanya sekali. Ide dengan penyetel publik bagus tetapi dalam kasus ini, kelas UnitTestDetector harus ditempatkan dalam rakitan bersama.
Sergey

13

Mungkin berguna, memeriksa ProcessName saat ini:

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

Dan fungsi ini juga harus diperiksa oleh unittest:

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

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


|| processName.StartsWith("testhost") // testhost.x86untuk VS 2019
Kiquenet

9

Dalam mode uji, Assembly.GetEntryAssembly()tampaknya null.

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

Perhatikan bahwa jika Assembly.GetEntryAssembly()adalah null, Assembly.GetExecutingAssembly()tidak.

The Dokumentasi mengatakan: GetEntryAssemblyMetode dapat kembali nullketika berhasil perakitan telah dimuat dari aplikasi unmanaged.


8

Di suatu tempat dalam proyek yang sedang diuji:

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

Di suatu tempat dalam proyek pengujian unit Anda:

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

Elegan, tidak. Tapi terus terang dan cepat. AssemblyInitializeradalah untuk MS Test. Saya berharap kerangka pengujian lain memiliki padanan.


1
Jika kode yang Anda uji membuat AppDomain tambahan, IsRunningInUnitTesttidak disetel ke true di AppDomain tersebut.
Edward Brey

Tapi itu bisa dengan mudah diselesaikan dengan menambahkan rakitan bersama atau mendeklarasikan IsRunningInUnitTest di setiap domain.
Sergey

3

Saya menggunakan ini hanya untuk melewatkan logika yang menonaktifkan semua TraceAppenders di log4net selama startup ketika tidak ada debugger yang terpasang. Hal ini memungkinkan pengujian unit untuk masuk ke jendela hasil Resharper bahkan saat berjalan dalam mode non-debug.

Metode yang menggunakan fungsi ini dipanggil saat aplikasi dimulai atau saat memulai perlengkapan uji.

Ini mirip dengan posting Ryan tetapi menggunakan LINQ, menjatuhkan persyaratan System.Reflection, tidak menyimpan hasilnya, dan bersifat pribadi untuk mencegah penyalahgunaan (tidak disengaja).

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

3

Memiliki referensi ke kerangka nunit tidak berarti bahwa pengujian benar-benar berjalan. Misalnya dalam Unity ketika Anda mengaktifkan tes mode putar, referensi biarawati ditambahkan ke proyek. Dan ketika Anda menjalankan permainan, referensi tersebut ada, sehingga UnitTestDetector tidak akan bekerja dengan benar.

Alih-alih memeriksa perakitan nunit, kita dapat meminta nunit api untuk memeriksa apakah kode sedang menjalankan pengujian sekarang atau tidak.

using NUnit.Framework;

// ...

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

Edit:

Berhati-hatilah karena TestContext dapat dibuat secara otomatis jika diperlukan.


2
Tolong jangan membuang kode di sini. Jelaskan apa yang dilakukannya.
nkr

3

Gunakan saja ini:

AppDomain.CurrentDomain.IsDefaultAppDomain()

Dalam mode uji, ini akan mengembalikan nilai salah.


1

Saya tidak senang mengalami masalah ini baru-baru ini. Saya menyelesaikannya dengan cara yang sedikit berbeda. Pertama, saya tidak ingin membuat asumsi bahwa framework nunit tidak akan pernah dimuat di luar lingkungan pengujian; Saya sangat khawatir tentang pengembang yang menjalankan aplikasi di mesin mereka. Jadi saya menjalankan tumpukan panggilan sebagai gantinya. Kedua, saya dapat membuat asumsi bahwa kode pengujian tidak akan pernah dijalankan terhadap rilis biner, jadi saya memastikan kode ini tidak ada dalam sistem rilis.

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
        }

Saya menemukan ide untuk menguji versi debug sambil mengirimkan versi rilis menjadi ide yang buruk secara umum.
Patrick M

1

bekerja seperti pesona

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

Tes unit akan melewati titik masuk aplikasi. Setidaknya untuk wpf, winforms dan aplikasi konsol main()tidak dipanggil.

Jika metode utama dipanggil daripada saat run-time , jika tidak, kita berada dalam mode pengujian unit :

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

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

0

Mempertimbangkan kode Anda berjalan secara normal di utas utama (gui) dari aplikasi formulir windows dan Anda ingin kode tersebut berperilaku berbeda saat menjalankan pengujian, Anda dapat memeriksa

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

Saya menggunakan ini untuk kode yang saya inginkan fire and forgotdalam aplikasi gui tetapi dalam pengujian unit saya mungkin memerlukan hasil yang dihitung untuk sebuah pernyataan dan saya tidak ingin mengacaukan beberapa utas yang sedang berjalan.

Bekerja untuk MSTest. Keuntungannya adalah kode saya tidak perlu memeriksa kerangka pengujian itu sendiri dan jika saya benar-benar membutuhkan perilaku async dalam pengujian tertentu, saya dapat mengatur SynchronizationContext saya sendiri.

Sadarilah bahwa ini bukan metode yang dapat diandalkan Determine if code is running as part of a unit testseperti yang diminta oleh OP karena kode dapat berjalan di dalam utas tetapi untuk skenario tertentu ini bisa menjadi solusi yang baik (juga: Jika saya sudah menjalankan dari utas latar belakang, itu mungkin tidak diperlukan untuk memulai yang baru).


0

Application.Current adalah null saat berjalan di bawah unit tester. Setidaknya untuk aplikasi WPF saya menggunakan penguji Unit MS. Itu adalah tes yang mudah dilakukan jika diperlukan. Juga, sesuatu yang perlu diingat saat menggunakan Application.Current dalam kode Anda.


0

Saya telah menggunakan berikut ini di VB dalam kode saya untuk memeriksa apakah kita berada dalam pengujian unit. secara khusus saya tidak ingin tes membuka Word

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

0

Bagaimana kalau menggunakan refleksi dan sesuatu seperti ini:

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

Rakitan panggilan akan menjadi tempat kasus pengujian Anda dan hanya menggantikan MainForm beberapa jenis yang ada di kode Anda yang sedang diuji.


-3

Ada solusi yang sangat sederhana juga saat Anda menguji kelas ...

Cukup berikan kelas tempat Anda menguji properti seperti ini:

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

Sekarang pengujian unit Anda dapat menyetel boolean "thisIsUnitTest" ke true, jadi dalam kode yang ingin Anda lewati, tambahkan:

   if (thisIsUnitTest)
   {
       return;
   } 

Lebih mudah dan lebih cepat daripada memeriksa majelis. Mengingatkan saya pada Ruby On Rails di mana Anda akan melihat apakah Anda berada di lingkungan TEST.


1
Saya pikir Anda tidak disukai di sini karena Anda mengandalkan tes itu sendiri untuk mengubah perilaku kelas.
Riegardt Steyn

1
Ini tidak lebih buruk dari semua jawaban lain di sini.
DonO
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.