C# 自定义NLog滤波器类的单元测试

C# 自定义NLog滤波器类的单元测试,c#,testing,nlog,C#,Testing,Nlog,在实现自定义NLog筛选器时,我试图依赖WhenMethodFilter,它接受lambda回调ShouldLogEvent。然而,要对回调lambda进行单元测试,我需要在生成配置的类中公开它,这并不理想——我讨厌仅仅为了测试而公开方法 private void ReconfigureNlog(object state) { var nlogConfig = ConstructNlogConfiguration(); foreach (var r

在实现自定义NLog筛选器时,我试图依赖
WhenMethodFilter
,它接受lambda回调
ShouldLogEvent
。然而,要对回调lambda进行单元测试,我需要在生成配置的类中公开它,这并不理想——我讨厌仅仅为了测试而公开方法

    private void ReconfigureNlog(object state)
    {
        var nlogConfig = ConstructNlogConfiguration();
        foreach (var rule in nlogConfig.LoggingRules)
        {
            rule.Filters.Add(new WhenMethodFilter(ShouldLogEvent));
        }

        // TODO: maybe no need to reconfigure every time but just modify filter reference?
        NLog.Web.NLogBuilder.ConfigureNLog(nlogConfig);
    }
另一种方法是继承
Filter
基类,并尝试用单元测试覆盖它。但问题是它没有公共接口:

internal FilterResult GetFilterResult(LogEventInfo logEvent)
protected abstract FilterResult Check(LogEventInfo logEvent);
这使得它也不可测试,除非我公开自己的方法。虽然这似乎是个小问题,但我很好奇是否有更好的办法。在我看来,将
GetFilterResult
设置为内部是绝对没有必要的,尽管它有点遵循最佳设计实践。想法

更新1。待测试类别的代码:

public class TenancyLogFilter: Filter
{
    private readonly ITenancyLoggingConfiguration _loggingConfigurationConfig;
    private readonly IHttpContextAccessor _httpContextAccessor;

    public TenancyLogFilter(ITenancyLoggingConfiguration loggingConfigurationConfig, IHttpContextAccessor httpContextAccessor)
    {
        _loggingConfigurationConfig = loggingConfigurationConfig;
        _httpContextAccessor = httpContextAccessor;
    }

    protected override FilterResult Check(LogEventInfo logEvent)
    {
        var result = FilterResult.Neutral;

        if (CanEmitEvent(logEvent.Level))
        {
            result = FilterResult.Log;
        }

        return result;
    }

    private LogLevel GetMinLogLevel()
    {
        var level = LogLevel.Trace;
        if (!_loggingConfigurationConfig.TenantMinLoggingLevel.Any())
            return level;
        var context = _httpContextAccessor?.HttpContext;

        if (context == null)
            return level;


        if (context.Request.Headers.TryGetValue(CustomHeaders.TenantId, out var tenantIdHeaders))
        {
            var currentTenant = tenantIdHeaders.First();
            if (_loggingConfigurationConfig.TenantMinLoggingLevel.ContainsKey(currentTenant))
            {
                level = _loggingConfigurationConfig.TenantMinLoggingLevel[currentTenant];
            }
        }

        return level;
    }

    private bool CanEmitEvent(LogLevel currentLogLevel)
    {
        return currentLogLevel >= GetMinLogLevel();
    }
}

通常丑陋的技巧是将方法
设置为内部
,然后将其添加到主项目中的AssemblyInfo.cs中:

using System;
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleToAttribute("Tenancy.UnitTests")]
然后,单元测试项目
将被允许对主项目的内部方法进行单元测试


另请参见

,以测试可以从subject类派生的类,从而公开调用被测成员所需的内容,因为目标成员受到保护

无法更改/访问内部成员,但在本例中,源代码显示它是一个简单的实现

/// <summary>
/// Gets the result of evaluating filter against given log event.
/// </summary>
/// <param name="logEvent">The log event.</param>
/// <returns>Filter result.</returns>
internal FilterResult GetFilterResult(LogEventInfo logEvent)
{
    return Check(logEvent);
}
其实现将封装在自定义
tenacyclogfilter
过滤器中所做的工作,并将其注入到目标类中

private readonly ILogEventAssessor service;

//...assumed injected service

private void ReconfigureNlog(object state) {
    var nlogConfig = ConstructNlogConfiguration();
    foreach (var rule in nlogConfig.LoggingRules) {
        rule.Filters.Add(new WhenMethodFilter(ShouldLogEvent));
    }

    // TODO: maybe no need to reconfigure every time but just modify filter reference?
    NLog.Web.NLogBuilder.ConfigureNLog(nlogConfig);
}

private FilterResult ShouldLogEvent(LogEventInfo logEvent) {
    return service.GetFilterResult(logEvent);
}

//...
现在真的没有必要测试第三方过滤器来验证您的逻辑


你可以测试你的
ILogEventAssessor
实现,以单独验证你的自定义逻辑。

谢谢,我不知道,但可能不会这样做。是的,两者都可以,尽管拥有
ILogEventAssessor
看起来只是为了使代码可测试而设计,感觉有点不必要。但我想这是一个偏好的问题。不管怎样,感谢您的详细解释,我个人更喜欢在测试的同时有一个测试类,它将公开测试。@Ivan在这种情况下,我同意这种方法更好。我将另一个作为替代方案,以表明有多种方法来解决这个问题。
public interface ILogEventAssessor {
    FilterResult GetFilterResult(LogEventInfo logEvent);
}
private readonly ILogEventAssessor service;

//...assumed injected service

private void ReconfigureNlog(object state) {
    var nlogConfig = ConstructNlogConfiguration();
    foreach (var rule in nlogConfig.LoggingRules) {
        rule.Filters.Add(new WhenMethodFilter(ShouldLogEvent));
    }

    // TODO: maybe no need to reconfigure every time but just modify filter reference?
    NLog.Web.NLogBuilder.ConfigureNLog(nlogConfig);
}

private FilterResult ShouldLogEvent(LogEventInfo logEvent) {
    return service.GetFilterResult(logEvent);
}

//...