簡體   English   中英

如何解決XUnit測試失敗的情況,在“全部運行”下的NLog目標中斷言消息

[英]How to troubleshoot the situation where XUnit tests fail with assertion of messages in NLog target under “Run All”

這是我的環境:

  • Visual Studio 2017
  • Project的.NET運行時版本是4.6.2
  • XUnit版本2.3.1
  • NLog版本4.4.12
  • 流利斷言4.19.4

這就是問題:

當我單獨運行測試時,它們會通過,但是當我通過測試資源管理器中的“全部運行”按鈕運行時,我會遇到故障,當重復運行后續失敗的任務時,所有這些都會最終通過。 還想指出我沒有並行運行測試。 測試的性質使得測試中的代碼發出日志信息,最終以自定義NLog目標結束。 這是一個示例程序,可以運行以重現問題。

using FluentAssertions;
using NLog;
using NLog.Common;
using NLog.Config;
using NLog.Targets;
using System;
using System.Collections.Concurrent;
using System.IO;
using Xunit;

namespace LoggingTests
{
    [Target("test-target")]
    public class TestTarget : TargetWithLayout
    {
        public ConcurrentBag<string> Messages = new ConcurrentBag<string>();

        public TestTarget(string name)
        {
            Name = name;
        }

        protected override void Write(LogEventInfo logEvent)
        {
            Messages.Add(Layout.Render(logEvent));
        }
    }

    class Loggable
    {
        private Logger _logger;

        public Loggable()
        {
            _logger = LogManager.GetCurrentClassLogger();
        }

        private void Log(LogLevel level,
                         Exception exception,
                         string message,
                         params object[] parameters)
        {
            LogEventInfo log_event = new LogEventInfo();
            log_event.Level = level;
            log_event.Exception = exception;
            log_event.Message = message;
            log_event.Parameters = parameters;
            log_event.LoggerName = _logger.Name;
            _logger.Log(log_event);
        }

        public void Debug(string message)
        {
            Log(LogLevel.Debug,
                null,
                message,
                null);
        }

        public void Error(string message)
        {
            Log(LogLevel.Error,
                null,
                message,
                null);
        }

        public void Info(string message)
        {
            Log(LogLevel.Info,
                null,
                message,
                null);
        }

        public void Fatal(string message)
        {
            Log(LogLevel.Fatal,
                null,
                message,
                null);
        }
    }

    public class Printer
    {
        public delegate void Print(string message);
        private Print _print_function;

        public Printer(Print print_function)
        {
            _print_function = print_function;
        }

        public void Run(string message_template,
                        int number_of_times)
        {
            for (int i = 0; i < number_of_times; i++)
            {
                _print_function($"{message_template} - {i}");
            }
        }
    }

    public abstract class BaseTest
    {
        protected string _target_name;

        public BaseTest(LogLevel log_level)
        {
            if (LogManager.Configuration == null)
            {
                LogManager.Configuration = new LoggingConfiguration();
                InternalLogger.LogLevel = LogLevel.Debug;
                InternalLogger.LogFile = Path.Combine(Environment.CurrentDirectory,
                                                      "nlog_debug.txt");
            }

            // Register target:
            _target_name = GetType().Name;
            Target.Register<TestTarget>(_target_name);

            // Create Target:
            TestTarget t = new TestTarget(_target_name);
            t.Layout = "${message}";

            // Add Target to configuration:
            LogManager.Configuration.AddTarget(_target_name,
                                               t);

            // Add a logging rule pertaining to the above target:
            LogManager.Configuration.AddRule(log_level,
                                             log_level,
                                             t);

            // Because configuration has been modified programatically, we have to reconfigure all loggers:
            LogManager.ReconfigExistingLoggers();
        }
        protected void AssertTargetContains(string message)
        {
            TestTarget target = (TestTarget)LogManager.Configuration.FindTargetByName(_target_name);
            target.Messages.Should().Contain(message);
        }
    }

    public class TestA : BaseTest
    {
        public TestA() : base(LogLevel.Info)
        {
        }

        [Fact]
        public void SomeTest()
        {
            int number_of_times = 100;
            (new Printer((new Loggable()).Info)).Run(GetType().Name, 
                                                     number_of_times);
            for (int i = 0; i < number_of_times; i++)
            {
                AssertTargetContains($"{GetType().Name} - {i}");
            }
        }
    }

    public class TestB : BaseTest
    {
        public TestB() : base(LogLevel.Debug)
        {
        }

        [Fact]
        public void SomeTest()
        {
            int number_of_times = 100;
            (new Printer((new Loggable()).Debug)).Run(GetType().Name,
                                                     number_of_times);
            for (int i = 0; i < number_of_times; i++)
            {
                AssertTargetContains($"{GetType().Name} - {i}");
            }
        }
    }

    public class TestC : BaseTest
    {
        public TestC() : base(LogLevel.Error)
        {
        }

        [Fact]
        public void SomeTest()
        {
            int number_of_times = 100;
            (new Printer((new Loggable()).Error)).Run(GetType().Name,
                                                     number_of_times);
            for (int i = 0; i < number_of_times; i++)
            {
                AssertTargetContains($"{GetType().Name} - {i}");
            }
        }
    }

    public class TestD : BaseTest
    {
        public TestD() : base(LogLevel.Fatal)
        {
        }

        [Fact]
        public void SomeTest()
        {
            int number_of_times = 100;
            (new Printer((new Loggable()).Fatal)).Run(GetType().Name,
                                                     number_of_times);
            for (int i = 0; i < number_of_times; i++)
            {
                AssertTargetContains($"{GetType().Name} - {i}");
            }
        }
    }
}

上面的測試代碼運行得更好。 按照消息進行一些早期的故障排除后,似乎我沒有調用LogManager.ReconfigExistingLoggers(); 因為配置是以編程方式創建的(在測試類的構造函數中)。 這是LogManager源代碼中的注釋:

/// Loops through all loggers previously returned by GetLogger.
/// and recalculates their target and filter list. Useful after modifying the configuration programmatically
/// to ensure that all loggers have been properly configured.

之后所有測試都按預期運行,偶爾出現如下所示的故障:

在此輸入圖像描述

我現在想知道在我的測試設置中是否還有其他更多的東西,或者這是一個NLog的錯誤。 任何關於如何修復我的測試設置或對設置進行故障排除的建議都是非常受歡迎的。 提前致謝。


更新

  • List<LogData>更改為ConcurrentBag<LogData> 然而,這並沒有改變問題。 問題仍然是消息沒有及時到達收集。
  • 重新設計問題並用一個實際的例子(可以運行以重現問題)替換以前的代碼示例+問題的屏幕截圖。
  • 改進的測試運行得更好但偶爾因NLog本身的異常而失敗(添加截圖)。

上述問題恰好出現在VisualStudio中的XUnit Runner中。 盡管我已經“禁止並行測試”,但測試仍以某種方式並行運行。 @ rolf-kristensen指出另一個NLog問題(參考: https//github.com/NLog/NLog/issues/2525 ),他已經添加了以下內容:

[assembly: Xunit.CollectionBehavior(DisableTestParallelization = true)]

AssemblyInfo.cs文件中。 此配置也在XUnit的頁面上提到(參考: https//xunit.github.io/docs/running-tests-in-parallel.html - 更改默認行為)

你已經展示了非常隨機的代碼,以及關於什么是失敗的非常隨機的細節。 所以也許我的建議對你的問題沒有意義。

您應該設置日志記錄配置,而不是直接調用TestLogTarget

var target = new TestLogTarget() { Name = "Test" };
NLog.Config.SimpleConfigurator(target);
var logger = NLog.LogManager.GetCurrentClassLogger();
logger.Info("Hello World");

確保圍繞對消息的訪問添加lock 通過在持有lock同時制作ToArray() (或在持有lock同時調用Contains

請記住,NLog是一個全局引擎,需要在單元測試環境中特別努力,其中測試類,測試應用程序域被停止並經常啟動,因此您需要了解您的單元測試系統和您的nlog系統來制作它們一起工作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM