Xác định xem mã có đang chạy như một phần của bài kiểm tra đơn vị hay không


105

Tôi có một bài kiểm tra đơn vị (nUnit). Nhiều lớp bên dưới ngăn xếp cuộc gọi, một phương thức sẽ không thành công nếu nó đang chạy qua một bài kiểm tra đơn vị.

Lý tưởng nhất là bạn sẽ sử dụng một cái gì đó như mocking để thiết lập đối tượng mà phương pháp này phụ thuộc vào nhưng đây là mã của bên thứ 3 và tôi không thể làm điều đó nếu không có nhiều công việc.

Tôi không muốn thiết lập các phương thức cụ thể của nUnit - có quá nhiều cấp độ ở đây và cách thực hiện kiểm tra đơn vị kém.

Thay vào đó, những gì tôi muốn làm là thêm một cái gì đó như thế này vào sâu trong ngăn xếp cuộc gọi

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

Vì vậy, bất kỳ ý tưởng về cách viết IsRunningInUnitTest?

Tái bút Tôi hoàn toàn biết rằng đây không phải là thiết kế tuyệt vời, nhưng tôi nghĩ nó tốt hơn các lựa chọn thay thế.


5
Bạn không nên trực tiếp hoặc gián tiếp thử nghiệm mã của bên thứ ba trong thử nghiệm đơn vị. Bạn nên tách riêng phương pháp đang thử nghiệm của mình khỏi việc triển khai của bên thứ ba.
Craig Stuntz

14
Vâng - tôi nhận ra rằng - trong một thế giới ý tưởng, nhưng đôi khi chúng ta phải thực dụng một chút về những thứ không?
Ryan

9
Quay lại nhận xét của Craig - không chắc điều đó đúng. Nếu phương pháp của tôi dựa trên thư viện của bên thứ 3 hoạt động theo một cách nhất định thì đây không phải là một phần của thử nghiệm? Nếu ứng dụng của bên thứ 3 thay đổi, tôi muốn thử nghiệm của mình không thành công. Nếu bạn đang sử dụng thử nghiệm chế nhạo cách bạn cho rằng ứng dụng của bên thứ 3 hoạt động, chứ không phải cách nó thực sự hoạt động.
Ryan

2
Ryan, bạn có thể kiểm tra các giả định về hành vi của bên thứ ba, nhưng đó là một thử nghiệm riêng biệt. Bạn cần kiểm tra mã của riêng mình một cách riêng biệt.
Craig Stuntz

2
Tôi hiểu những gì bạn nói nhưng vì bất cứ điều gì ngoài một ví dụ nhỏ mà bạn sẽ nói về một khối lượng công việc lớn (khổng lồ) và không có gì để đảm bảo rằng các giả định bạn kiểm tra trong bài kiểm tra của bạn giống với giả định của bạn trong các phương pháp thực tế của bạn . Hmm - tranh luận về một bài đăng trên blog, tôi nghĩ, tôi sẽ gửi cho bạn một email khi tôi đã tổng hợp được suy nghĩ của mình.
Ryan

Câu trả lời:


80

Tôi đã làm điều này trước đây - tôi đã phải giữ mũi khi làm việc đó, nhưng tôi đã làm được. Chủ nghĩa thực dụng đánh bại chủ nghĩa giáo điều mọi lúc. Tất nhiên, nếu có một cách tốt đẹp, bạn có thể cấu trúc lại để tránh nó, đó sẽ là tuyệt vời.

Về cơ bản, tôi có một lớp "UnitTestDetector" để kiểm tra xem lắp ráp khung NUnit đã được tải trong AppDomain hiện tại hay chưa. Nó chỉ cần làm điều này một lần, sau đó lưu kết quả vào bộ nhớ cache. Xấu xí, nhưng đơn giản và hiệu quả.


bất kỳ mẫu nào về UnitTestDetector? và tương tự cho MSTest?
Kiquenet

4
@Kiquenet: Tôi nghĩ tôi chỉ sử dụng AppDomain.GetAssembliesvà kiểm tra lắp ráp có liên quan - đối với MSTest, bạn cần phải xem những lắp ráp nào được tải. Hãy xem câu trả lời của Ryan để làm ví dụ.
Jon Skeet

Đây không phải là một cách tiếp cận tốt đối với tôi. Tôi đang gọi phương thức UnitTest từ Ứng dụng Console và nó cho rằng đó là Ứng dụng UnitTest.
Bizhan

1
@Bizhan: Khi đó, tôi khuyên bạn nên ở trong một tình huống khá chuyên biệt và bạn không nên mong đợi những câu trả lời chung chung hơn sẽ hiệu quả. Bạn có thể muốn đặt một câu hỏi mới với tất cả các yêu cầu cụ thể của bạn. (Sự khác nhau giữa "mã của bạn gọi từ một ứng dụng giao diện điều khiển" và "một Á hậu thử nghiệm" ví dụ thế nào bạn sẽ muốn phân biệt giữa ứng dụng giao diện điều khiển của bạn và bất kỳ runner thử nghiệm giao diện điều khiển dựa trên khác là gì?)
Jon Skeet

74

Lấy ý tưởng của Jon, đây là điều tôi nghĩ ra -

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

Nhìn xuống phía sau, tất cả chúng ta đều là những cậu bé đủ lớn để nhận ra khi nào chúng ta đang làm điều gì đó mà chúng ta có thể không nên;)


2
+1 Câu trả lời hay. Tuy nhiên, điều này có thể được đơn giản hóa một chút, hãy xem bên dưới: stackoverflow.com/a/30356080/184528
cdiggins

Dự án cụ thể mà tôi đã viết điều này cho (và vẫn là!) .NET 2.0 nên không có linq.
Ryan

Việc sử dụng này có hiệu quả với tôi nhưng có vẻ như tên lắp ráp đã thay đổi kể từ đó. Tôi chuyển sang giải pháp Kiquenet của
The_Black_Smurf

Tôi phải tắt khai thác gỗ cho travis ci xây dựng, nó đóng băng mọi thứ
jjxtra

Làm việc cho tôi, tôi phải hack xung quanh lỗi .NET lõi 3 bằng dao cạo chỉ xảy ra trong các bài kiểm tra đơn vị.
jjxtra

62

Phỏng theo câu trả lời của Ryan. Cái này dành cho khung kiểm tra đơn vị MS.

Lý do tôi cần điều này là vì tôi hiển thị một MessageBox về lỗi. Nhưng các bài kiểm tra đơn vị của tôi cũng kiểm tra mã xử lý lỗi và tôi không muốn MessageBox bật lên khi chạy kiểm tra đơn vị.

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

Và đây là một bài kiểm tra đơn vị cho nó:

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

8
Tôi có một cách tốt hơn để giải quyết vấn đề MessageBox của bạn và ngăn chặn sự tấn công này và cung cấp nhiều trường hợp thử nghiệm đơn vị hơn. Tôi sử dụng một lớp thực hiện một giao diện mà tôi gọi là ICommonDialogs. Lớp thực thi hiển thị tất cả các hộp thoại bật lên (MessageBox, hộp thoại Tệp, bộ chọn màu, hộp thoại kết nối cơ sở dữ liệu, v.v.). Các lớp cần hiển thị hộp thông báo chấp nhận ICommonDiaglogs làm tham số khởi tạo mà sau đó chúng ta có thể giả lập trong bài kiểm tra đơn vị. Phần thưởng: Bạn có thể xác nhận về các cuộc gọi MessageBox dự kiến.
Tony O'Hagan

1
@Tony, ý kiến ​​hay. Đó rõ ràng là cách tốt nhất để làm điều đó. Tôi không biết mình đang nghĩ gì lúc đó. Tôi nghĩ rằng tiêm thuốc phụ thuộc vẫn còn mới mẻ đối với tôi vào thời điểm đó.
dan-gph

3
Nghiêm túc mà nói, mọi người, hãy tìm hiểu về tiêm thuốc phụ thuộc, và thứ hai, các đối tượng giả. Việc tiêm phụ thuộc sẽ cách mạng hóa lập trình của bạn.
dan-gph

2
Tôi sẽ triển khai UnitTestDetector.IsInUnitTest là "trả về true" và bài kiểm tra đơn vị của bạn sẽ vượt qua. ;) Một trong những điều buồn cười mà dường như không thể kiểm tra đơn vị.
Samer Adra

1
Microsoft.VisualStudio.QualityTools.UnitTestFramework không hoạt động với tôi nữa. Đã thay đổi nó thành Microsoft.VisualStudio.TestPlatform.TestFramework - hoạt động trở lại.
Alexander

21

Đơn giản hóa giải pháp của Ryan, bạn chỉ có thể thêm thuộc tính tĩnh sau vào bất kỳ lớp nào:

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

2
Khá giống với câu trả lời của dan-gph (mặc dù đó là tìm kiếm bộ công cụ VS, không phải nunit).
Ryan

18

Tôi sử dụng một cách tiếp cận tương tự như highseth

Đây là mã cơ bản có thể dễ dàng sửa đổi để đưa vào bộ nhớ đệm. Một ý tưởng hay khác là thêm một setter vào IsRunningInUnitTestvà gọi UnitTestDetector.IsRunningInUnitTest = falseđến điểm nhập chính của dự án của bạn để tránh thực thi mã.

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

Tôi thích cách tiếp cận này hơn những câu trả lời được bình chọn cao hơn. Tôi không nghĩ rằng sẽ an toàn khi cho rằng các cụm kiểm thử đơn vị sẽ chỉ được tải trong quá trình kiểm tra đơn vị và tên quy trình cũng có thể khác nhau giữa các nhà phát triển (ví dụ: một số sử dụng trình chạy kiểm tra R #).
EM0

Cách tiếp cận này sẽ hoạt động nhưng nó sẽ tìm kiếm các thuộc tính đó mỗi khi IsRunningInUnitTest getter được gọi. Có thể có một số trường hợp mà nó có thể ảnh hưởng đến hiệu suất. Kiểm tra AssemblyName rẻ hơn vì nó chỉ được thực hiện một lần. Ý tưởng với public setter là tốt nhưng trong trường hợp này, lớp UnitTestDetector nên được đặt trong một assembly chia sẻ.
Sergey

13

Có thể hữu ích, hãy kiểm tra ProcessName hiện tại:

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

Và chức năng này cũng nên được kiểm tra bằng cách:

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

Tài liệu tham khảo:
Matthew Watson trong http://social.msdn.microsoft.com/Forums/en-US/csharplanguage/thread/11e68468-c95e-4c43-b02b-7045a52b407e/


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

9

Trong chế độ thử nghiệm, Assembly.GetEntryAssembly()dường như được null.

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

Lưu ý rằng nếu Assembly.GetEntryAssembly()null, Assembly.GetExecutingAssembly()thì không.

Các tài liệu cho biết: Các GetEntryAssemblyphương pháp có thể trở lại nullkhi một quản lý lắp ráp đã được tải từ một ứng dụng không được quản lý.


8

Một nơi nào đó trong dự án đang được thử nghiệm:

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

Ở đâu đó trong dự án thử nghiệm đơn vị của bạn:

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

Thanh lịch, không. Nhưng đơn giản và nhanh chóng. AssemblyInitializerdành cho Thử nghiệm MS. Tôi mong đợi các khuôn khổ thử nghiệm khác có các khung tương đương.


1
Nếu mã bạn đang thử nghiệm tạo thêm AppDomains, IsRunningInUnitTestkhông được đặt thành true trong các AppDomains đó.
Edward Brey,

Nhưng nó có thể được giải quyết dễ dàng bằng cách thêm một assembly chia sẻ hoặc khai báo IsRunningInUnitTest trong mỗi miền.
Sergey

3

Tôi sử dụng này chỉ cho bỏ qua logic mà vô hiệu hóa tất cả TraceAppenders trong log4net trong khởi động khi không có debugger được đính kèm. Điều này cho phép các bài kiểm tra đơn vị đăng nhập vào cửa sổ kết quả Resharper ngay cả khi chạy ở chế độ không gỡ lỗi.

Phương thức sử dụng chức năng này được gọi khi khởi động ứng dụng hoặc khi bắt đầu một lịch thi đấu.

Nó tương tự như bài đăng của Ryan nhưng sử dụng LINQ, bỏ yêu cầu System.Reflection, không lưu kết quả vào bộ nhớ cache và ở chế độ riêng tư để ngăn chặn việc lạm dụng (vô tình).

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

3

Có một tham chiếu đến khung nunit không có nghĩa là thử nghiệm đó thực sự đang chạy. Ví dụ trong Unity khi bạn kích hoạt kiểm tra chế độ chơi, các tham chiếu nunit được thêm vào dự án. Và khi bạn chạy một trò chơi, các tham chiếu vẫn tồn tại, vì vậy UnitTestDetector sẽ không hoạt động chính xác.

Thay vì kiểm tra lắp ráp nunit, chúng ta có thể yêu cầu nunit api kiểm tra xem mã có đang thực thi kiểm tra ngay bây giờ hay không.

using NUnit.Framework;

// ...

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

Biên tập:

Lưu ý rằng TestContext có thể được tạo tự động nếu nó được yêu cầu.


2
Xin đừng chỉ kết xuất mã ở đây. Giải thích những gì nó làm.
nkr

2

Chỉ cần sử dụng cái này:

AppDomain.CurrentDomain.IsDefaultAppDomain()

Trong chế độ thử nghiệm, nó sẽ trả về false.


1

Tôi không vui khi có vấn đề này gần đây. Tôi đã giải quyết nó theo một cách hơi khác. Đầu tiên, tôi không muốn đưa ra giả định rằng khung công tác nunit sẽ không bao giờ được tải bên ngoài môi trường thử nghiệm; Tôi đặc biệt lo lắng về việc các nhà phát triển chạy ứng dụng trên máy của họ. Vì vậy, tôi đã đi bộ cuộc gọi thay thế. Thứ hai, tôi có thể đưa ra giả định rằng mã thử nghiệm sẽ không bao giờ được chạy dựa trên các mã nhị phân phát hành, vì vậy tôi đảm bảo rằng mã này không tồn tại trong hệ thống phát hành.

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
        }

Tôi thấy ý tưởng thử nghiệm phiên bản gỡ lỗi trong khi vận chuyển phiên bản phát hành nói chung là một ý tưởng tồi.
Patrick M

1

hoạt động như một sự quyến rũ

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

Bài kiểm tra đơn vị sẽ bỏ qua điểm đầu vào của ứng dụng. Ít nhất đối với wpf, ứng dụng winforms và console main()không được gọi.

Nếu phương thức chính được gọi hơn chúng ta đang trong thời gian chạy , nếu không, chúng ta đang ở chế độ kiểm tra đơn vị :

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

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

0

Xem xét mã của bạn là bình thường khi chạy trong chuỗi chính (gui) của ứng dụng biểu mẫu windows và bạn muốn nó hoạt động khác khi chạy trong một thử nghiệm, bạn có thể kiểm tra

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

Tôi đang sử dụng điều này cho mã mà tôi muốn fire and forgottrong ứng dụng gui nhưng trong các bài kiểm tra đơn vị, tôi có thể cần kết quả được tính toán để xác nhận và tôi không muốn gây rối với nhiều luồng đang chạy.

Hoạt động cho MSTest. Ưu điểm là mã của tôi không cần phải kiểm tra chính khung thử nghiệm và nếu tôi thực sự cần hành vi không đồng bộ trong một thử nghiệm nhất định, tôi có thể đặt SynchronizationContext của riêng mình.

Lưu ý rằng đây không phải là một phương pháp đáng tin cậy Determine if code is running as part of a unit testtheo yêu cầu của OP vì mã có thể đang chạy bên trong một chuỗi nhưng đối với một số trường hợp nhất định thì đây có thể là một giải pháp tốt (ngoài ra: Nếu tôi đang chạy từ một chuỗi nền, thì có thể không cần thiết để bắt đầu một cái mới).


0

Application.Current là rỗng khi chạy dưới trình kiểm tra đơn vị. Ít nhất là đối với ứng dụng WPF của tôi bằng cách sử dụng MS Unit tester. Đó là một bài kiểm tra dễ thực hiện nếu cần. Ngoài ra, một số điều cần lưu ý khi sử dụng Application.Current trong mã của bạn.


0

Tôi đã sử dụng phần sau trong VB trong mã của mình để kiểm tra xem chúng ta có đang kiểm tra đơn vị hay không. đặc biệt là tôi không muốn bài kiểm tra mở Word

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

0

Làm thế nào về việc sử dụng phản chiếu và những thứ tương tự như thế này:

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

Hợp ngữ gọi sẽ là nơi chứa các trường hợp thử nghiệm của bạn và chỉ thay thế cho MainForm một số loại trong mã của bạn đang được thử nghiệm.


-3

Có một giải pháp thực sự đơn giản khi bạn đang kiểm tra một lớp ...

Đơn giản chỉ cần cung cấp cho lớp bạn đang kiểm tra một thuộc tính như sau:

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

Giờ đây, bài kiểm tra đơn vị của bạn có thể đặt boolean "thisIsUnitTest" thành true, vì vậy, trong đoạn mã bạn muốn bỏ qua, hãy thêm:

   if (thisIsUnitTest)
   {
       return;
   } 

Nó dễ dàng và nhanh hơn so với việc kiểm tra các cụm lắp ráp. Làm tôi nhớ đến Ruby On Rails, nơi bạn muốn xem liệu mình có đang ở trong môi trường TEST hay không.


1
Tôi nghĩ rằng bạn đã bị bỏ phiếu ở đây vì bạn đang dựa vào chính bài kiểm tra để sửa đổi hành vi của lớp.
Riegardt Steyn,

1
Đây không phải là điều tồi tệ nhất so với tất cả các câu trả lời khác ở đây.
DonO
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.