C# Visual Studio 2017代码覆盖率报告异步方法的部分覆盖率

C# Visual Studio 2017代码覆盖率报告异步方法的部分覆盖率,c#,unit-testing,visual-studio-2017,code-coverage,xunit,C#,Unit Testing,Visual Studio 2017,Code Coverage,Xunit,我正在为一个简单的asp.net核心中间件进行一个小单元测试,并试图确定是否有可能在这个非常基本的场景中获得100%的覆盖率。我正在使用Visual Studio 2017>“分析代码覆盖率”、xUnit和Moq以获得完整性。在我的异步方法(如下所示)中,代码分析只报告部分覆盖。有没有办法完全覆盖这些 //示例中间件 internal sealed partial class JsonExceptionMiddleware { private const string DefaultEr

我正在为一个简单的asp.net核心中间件进行一个小单元测试,并试图确定是否有可能在这个非常基本的场景中获得100%的覆盖率。我正在使用Visual Studio 2017>“分析代码覆盖率”、xUnit和Moq以获得完整性。在我的异步方法(如下所示)中,代码分析只报告部分覆盖。有没有办法完全覆盖这些

//示例中间件

internal sealed partial class JsonExceptionMiddleware
{
    private const string DefaultErrorMessage = "A server error occurred.";
    private readonly RequestDelegate _next;
    private readonly ILogger<JsonExceptionMiddleware> _logger;


    public JsonExceptionMiddleware(RequestDelegate next, ILoggerFactory loggerFactory, IHostingEnvironment hostingEnvironment)
    {
        _next = next ?? throw new ArgumentNullException(nameof(next));
        _logger = loggerFactory?.CreateLogger<JsonExceptionMiddleware>() ?? throw new ArgumentNullException(nameof(loggerFactory));

        IncludeExceptionMessage = hostingEnvironment.IsDevelopment();
        IncludeExceptionStackTrace = hostingEnvironment.IsDevelopment();
    }


    /// <summary>
    /// Gets or sets whether the <see cref="Exception.StackTrace"/> should be included in the response message.
    /// </summary>
    public bool IncludeExceptionStackTrace { get; set; }

    /// <summary>
    /// Gets or sets whether the <see cref="Exception.Message"/> should be included in the response message.
    /// </summary>
    public bool IncludeExceptionMessage { get; set; }

    /// <summary>
    /// Implements the <see cref="RequestDelegate"/> so this class can be used as middleware.
    /// </summary>
    /// <param name="context">The current <see cref="HttpContext"/>.</param>
    /// <returns>A <see cref="Task"/> that completes when the error message is flush to the HTTP response.</returns>
    public async Task Invoke(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception ex)
        {
            if (context.Response.HasStarted) throw;

            context.Response.Clear();
            context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
            context.Response.ContentType = ApiConstants.Http.JsonContentType;

            ApiError error = BuildError(ex);

            await context.Response.WriteAsync(JsonConvert.SerializeObject(error, new JsonSerializerSettings(){ NullValueHandling = NullValueHandling.Ignore}));
        }
    }

    private ApiError BuildError(Exception ex)
    {
        string message = DefaultErrorMessage;
        string detail = null;
        string stack = null;

        if (IncludeExceptionMessage)
            detail = ex.Message;

        if (IncludeExceptionStackTrace)
            stack = ex.StackTrace;

        var error = new ApiError(message, detail, stack);
        return error;
    }
}
内部密封部分类JsonExceptionMiddleware
{
private const string DefaultErrorMessage=“发生服务器错误。”;
private readonly RequestDelegate\u next;
专用只读ILogger\u记录器;
公共JsonExceptionMiddleware(RequestDelegate next、ILoggerFactory loggerFactory、IHostingEnvironment hostingEnvironment)
{
_next=next??抛出新ArgumentNullException(nameof(next));
_logger=loggerFactory?.CreateLogger()??抛出新的ArgumentNullException(nameof(loggerFactory));
IncludeExceptionMessage=hostingEnvironment.IsDevelopment();
IncludeExceptionStackTrace=hostingEnvironment.IsDevelopment();
}
/// 
///获取或设置是否应在响应消息中包含。
/// 
公共bool IncludeExceptionStackTrace{get;set;}
/// 
///获取或设置是否应在响应消息中包含。
/// 
公共bool IncludeExceptionMessage{get;set;}
/// 
///实现,因此该类可以用作中间件。
/// 
///电流。
///当错误消息刷新到HTTP响应时完成的。
公共异步任务调用(HttpContext上下文)
{
尝试
{
等待下一步(上下文);
}
捕获(例外情况除外)
{
if(context.Response.HasStarted)抛出;
context.Response.Clear();
context.Response.StatusCode=(int)HttpStatusCode.InternalServerError;
context.Response.ContentType=ApiConstants.Http.JsonContentType;
APIRERROR error=构建错误(ex);
wait context.Response.WriteAsync(JsonConvert.SerializeObject(错误,新的JsonSerializerSettings(){NullValueHandling=NullValueHandling.Ignore}));
}
}
私有APIRROR构建错误(异常ex)
{
字符串消息=DefaultErrorMessage;
字符串详细信息=null;
字符串堆栈=null;
如果(包括异常消息)
细节=例如消息;
如果(包括异常跟踪)
stack=ex.StackTrace;
var error=新的APIRROR(消息、详细信息、堆栈);
返回误差;
}
}
蓝色=覆盖,黄色=部分覆盖,红色=未覆盖

//样本单元测试

    [Fact]
    public async Task SampleUnit()
    {
        // arrange

        var environment = new Mock<IHostingEnvironment>();
        environment
            .SetupGet(x => x.EnvironmentName)
            .Returns(EnvironmentName.Development);

        var response = new Mock<HttpResponse>();
        response
            .Setup(x => x.HasStarted)
            .Returns(true);

        var httpContext = new Mock<HttpContext>();
        httpContext
            .SetupGet(x => x.Response)
            .Returns(response.Object);

        var loggerFactory = new Mock<LoggerFactory>();

        var jsonExceptionMiddleware = new JsonExceptionMiddleware((innerHttpContext) => throw new Exception(SampleExceptionDetail), loggerFactory.Object, environment.Object);

        // act & assert

        await Assert.ThrowsAsync<Exception>(async () => await jsonExceptionMiddleware.Invoke(httpContext.Object).ConfigureAwait(false));
    }
[事实]
公共异步任务SampleUnit()
{
//安排
var-environment=newmock();
环境
.SetupGet(x=>x.EnvironmentName)
.Returns(EnvironmentName.Development);
var response=newmock();
响应
.Setup(x=>x.HasStarted)
.返回(真);
var httpContext=new Mock();
httpContext
.SetupGet(x=>x.Response)
.Returns(response.Object);
var loggerFactory=new Mock();
var jsonExceptionMiddleware=新的jsonExceptionMiddleware((innerHttpContext)=>抛出新异常(SampleExceptionDetail)、loggerFactory.Object、environment.Object);
//行动与主张
await Assert.ThrowsAsync(async()=>await jsonExceptionMiddleware.Invoke(httpContext.Object).ConfigureAwait(false));
}

从覆盖代码的外观上看,测试抛出等待,并且只流经catch块

通过不在请求委托中引发异常,允许
wait
流程完成。使用提供的示例测试,您需要像这样初始化中间件

//...

var jsonExceptionMiddleware = new JsonExceptionMiddleware((context) => Task.CompletedTask, 
    loggerFactory.Object, environment.Object);

//...

对于其他未覆盖的代码,您只需确保在等待时像现在一样抛出错误,但请确保
context.Response.HasStarted
true

编写更多涵盖所有执行路径的测试方法我有5个单元涵盖所有代码路径。从我到目前为止所做的研究来看,是关于异步方法被编译到的状态机,其中包含一条永远不会被采用的路径??对于这个MoveNext()问题,我还没有找到一个具体的答案。所以,即使您没有在请求委托中抛出异常,它也不会通过
new JsonExceptionMiddleware((context)=>Task.CompletedTask),…
对于另一个,您只需确保抛出错误并且
context.Response.HasStarted
是正确的。我很感激它——就是这样。谢谢