C# 如何排除XUnit测试失败的情况,并在NLog目标中断言消息;“全部运行”; 这是我的环境: Visual Studio 2017 Project的.NET运行时版本为4.6.2 XUnit版本2.3.1 NLog版本4.4.12 流畅的断言4.19.4 这就是问题所在:

C# 如何排除XUnit测试失败的情况,并在NLog目标中断言消息;“全部运行”; 这是我的环境: Visual Studio 2017 Project的.NET运行时版本为4.6.2 XUnit版本2.3.1 NLog版本4.4.12 流畅的断言4.19.4 这就是问题所在:,c#,unit-testing,continuous-integration,nlog,xunit.net,C#,Unit Testing,Continuous Integration,Nlog,Xunit.net,当我单独运行测试时,它们通过,但是当我通过测试资源管理器中的“run All”按钮运行时,我会失败,当重复运行后续失败的任务时,它们最终都通过。我还想指出,我并没有并行运行这些测试。测试的性质是,被测试的代码会发出日志信息,这些信息最终会出现在自定义NLog目标中。下面是一个示例程序,可以运行该程序来重现问题 using FluentAssertions; using NLog; using NLog.Common; using NLog.Config; using NLog.Targets;

当我单独运行测试时,它们通过,但是当我通过测试资源管理器中的“run All”按钮运行时,我会失败,当重复运行后续失败的任务时,它们最终都通过。我还想指出,我并没有并行运行这些测试。测试的性质是,被测试的代码会发出日志信息,这些信息最终会出现在自定义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}");
            }
        }
    }
}
之后,所有测试按预期运行,偶尔出现如下所示的故障:

我现在想知道,在我的测试设置中是否还有其他需要保护的内容,或者这是一个bug NLog。任何关于如何修复我的测试设置或对设置进行故障排除的建议都是非常受欢迎的。提前谢谢


更新
  • 列表
    更改为
    ConcurrentBag
    。然而,这并不能改变问题。问题仍然是消息并没有及时到达集合中
  • 重新制定了问题,并用实际示例(可以运行该示例来重现问题)+问题的屏幕截图替换了以前的代码示例
  • 改进的测试运行得更好,但偶尔由于NLog本身的异常而失败(添加了屏幕截图)

您显示的代码非常随机,关于失败原因的详细信息非常随机。所以也许我的建议对你的问题没有意义

与其直接调用
TestLogTarget
,不如设置日志配置:

var target = new TestLogTarget() { Name = "Test" };
NLog.Config.SimpleConfigurator(target);
var logger = NLog.LogManager.GetCurrentClassLogger();
logger.Info("Hello World");
确保在访问消息的周围添加一个
。在按住
锁的同时发出
ToArray()
(或在按住
锁的同时调用
Contains


请记住,NLog是一个全局引擎,在单元测试环境中,测试类、测试应用程序域经常停止和启动,因此您需要了解您的单元测试系统和NLog系统,使它们能够协同工作。

上述问题的问题恰好与VisualStudio中的XUnit Runner有关。尽管我禁用了“不要并行运行测试”,但测试还是以某种方式并行运行@rolf kristensen在另一个NLog问题(参考:)中指出,他添加了以下内容:

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

AssemblyInfo.cs
文件中。XUnit的页面上也提到了此配置(参考:-更改默认行为)

可能是共享资源测试中的线程问题。尝试包装
邮件。在
锁中添加
。我确信,如果每个测试都有自己的日志目标实例,那么一切都会正常工作。即使我将
List
更改为
ConcurrentBag
,问题仍然存在:消息没有及时到达集合。我想我会在目标的
Write()
方法中添加一个触发器,以便最终回调到测试方法并运行断言。您是否尝试将StringWriter附加到NLog.Common.InternalLogger.LogWriter并使用Console.WriteLine输出结果(应由visual studio单元测试运行程序选择)我使用了
InternalLogger.LogFile
,但无法跟踪失败,例如NLog本身引发的异常。但是,我的测试设置如上所述有所改进。即使我将
列表
更改为
ConcurrentBag
也会重复此设置,但问题仍然存在:消息没有及时到达集合。我想我会在目标的
Write()
方法中添加一个触发器,以便最终回调到测试方法并运行断言。很抱歉,如果我不清楚失败的原因是什么:假设(数据结构消息)中存在给定日志消息的断言包含所有日志消息。此外,我正在通过SimpleConfigurator注册目标,就像您在上面所做的那样(参考:构造函数)。我重新制定了问题,并添加了可以运行的代码(必须安装依赖项:
XUnit
FluentAssertions
NLog
),以便重现问题(请参见问题描述中新增的屏幕截图)。NLog项目正在使用xUnit,除了日志记录之外什么也不做,并验证日志记录是否正确。我查看了github上的NLog测试,但无法获得相同的设置。例如,所有测试都继承自
NLogTestBase
及其构造函数中的
Close()
LogManager.Configuration
上调用。但是,在
LoggingConfiguration
上没有
Close()
方法。这与调用
LogManager.Configuration=null相同;
这解决了我的问题,但我没有在任何依赖代码中使用NLog,我使用的是TPL数据流。
[assembly: Xunit.CollectionBehavior(DisableTestParallelization = true)]