1

У меня есть следующий метод:Может ли этот метод достичь покрытия на 100% кода?

public static async Task<bool> CreateIfNotExistsAsync(
    this ISegment segment, 
    CancellationToken cancellationToken) 
{ 
    Requires.ArgNotNull(segment, nameof(segment)); 

    try 
    { 
     await segment.CreateAsync(cancellationToken); 
     return true; 
    } 
    catch (Exception error) 
    { 
     if (error.CanSuppress() && !cancellationToken.IsCancellationRequested) 
     { 
      var status = await segment.GetStatusAsync(cancellationToken); 
      if (status.Exists) 
      { 
       return false; 
      } 
     } 

     throw; 
    } 
} 

... для которого я написал тесты, которые должны охватывать все блоки. Однако; Результаты покрытия кода (в Visual Studio 2015 Update 3) показывает, что два блока не распространяется на:

Two blocks not covered

Я полагал, что это было что-то делать с кодом, который генерируется для await внутри броской блока, поэтому я попытался переписывания метод, как это:

public static async Task<bool> CreateIfNotExistsAsync(
    this ISegment segment, 
    CancellationToken cancellationToken) 
{ 
    Requires.ArgNotNull(segment, nameof(segment)); 

    ExceptionDispatchInfo capturedError; 

    try 
    { 
     await segment.CreateAsync(cancellationToken); 
     return true; 
    } 
    catch (Exception error) 
    { 
     if (error.CanSuppress() && !cancellationToken.IsCancellationRequested) 
     { 
      capturedError = ExceptionDispatchInfo.Capture(error); 
     } 
     else 
     { 
      throw; 
     } 
    } 

    var status = await segment.GetStatusAsync(cancellationToken); 
    if (!status.Exists) 
    { 
     capturedError.Throw(); 
    } 

    return false; 
} 

Однако, есть еще один блок, который не охватывается:

Still not fully covered

Можно ли переписать этот метод так, чтобы он мог быть полностью покрыт?


Вот мои родственные испытания:

[TestMethod] 
public async Task Create_if_not_exists_returns_true_when_create_succeed() 
{ 
    var mock = new Mock<ISegment>(); 
    Assert.IsTrue(await mock.Object.CreateIfNotExistsAsync(default(CancellationToken))); 
    mock.Verify(_ => _.CreateAsync(It.IsAny<CancellationToken>()), Times.Once); 
} 

[TestMethod] 
public async Task Create_if_not_exists_throws_when_create_throws_and_cancellation_is_requested() 
{ 
    var mock = new Mock<ISegment>(); 
    var exception = new Exception(); 

    mock.Setup(_ => _.CreateAsync(It.IsAny<CancellationToken>())).Throws(exception); 

    try 
    { 
     await mock.Object.CreateIfNotExistsAsync(new CancellationToken(true)); 
     Assert.Fail(); 
    } 
    catch (Exception caught) 
    { 
     Assert.AreSame(exception, caught); 
    } 

    mock.Verify(_ => _.CreateAsync(It.IsAny<CancellationToken>()), Times.Once); 
    mock.Verify(_ => _.GetStatusAsync(It.IsAny<CancellationToken>()), Times.Never); 
} 

[TestMethod] 
public async Task Create_if_not_exists_throws_when_create_throws_non_suppressable_exception() 
{ 
    var mock = new Mock<ISegment>(); 
    var exception = new OutOfMemoryException(); 

    mock.Setup(_ => _.CreateAsync(It.IsAny<CancellationToken>())).Throws(exception); 

    try 
    { 
     await mock.Object.CreateIfNotExistsAsync(default(CancellationToken)); 
     Assert.Fail(); 
    } 
    catch (Exception caught) 
    { 
     Assert.AreSame(exception, caught); 
    } 

    mock.Verify(_ => _.CreateAsync(It.IsAny<CancellationToken>()), Times.Once); 
    mock.Verify(_ => _.GetStatusAsync(It.IsAny<CancellationToken>()), Times.Never); 
} 

[TestMethod] 
public async Task Create_if_not_exists_throws_when_create_throws_and_status_says_segment_doesnt_exists() 
{ 
    var mock = new Mock<ISegment>(); 
    var exception = new Exception(); 

    mock.Setup(_ => _.CreateAsync(It.IsAny<CancellationToken>())).Throws(exception); 
    mock.Setup(_ => _.GetStatusAsync(It.IsAny<CancellationToken>())) 
     .ReturnsAsync(new SegmentStatus(false, false, null, 0)); 

    try 
    { 
     await mock.Object.CreateIfNotExistsAsync(default(CancellationToken)); 
     Assert.Fail(); 
    } 
    catch (Exception caught) 
    { 
     Assert.AreSame(exception, caught); 
    } 

    mock.Verify(_ => _.CreateAsync(It.IsAny<CancellationToken>()), Times.Once); 
    mock.Verify(_ => _.GetStatusAsync(It.IsAny<CancellationToken>()), Times.Once); 
} 

[TestMethod] 
public async Task Create_if_not_exists_returns_false_when_create_throws_and_status_says_segment_exists() 
{ 
    var mock = new Mock<ISegment>(); 

    mock.Setup(_ => _.CreateAsync(It.IsAny<CancellationToken>())).Throws<Exception>(); 
    mock.Setup(_ => _.GetStatusAsync(It.IsAny<CancellationToken>())) 
     .ReturnsAsync(new SegmentStatus(true, false, null, 0)); 

    Assert.IsFalse(await mock.Object.CreateIfNotExistsAsync(default(CancellationToken))); 

    mock.Verify(_ => _.CreateAsync(It.IsAny<CancellationToken>()), Times.Once); 
    mock.Verify(_ => _.GetStatusAsync(It.IsAny<CancellationToken>()), Times.Once); 
} 

Это CanSuppress -logic:

private static readonly Exception[] EmptyArray = new Exception[0]; 

/// <summary> 
///  Determines whether an <see cref="Exception"/> can be suppressed. 
/// </summary> 
/// <param name="exception"> 
///  The <see cref="Exception"/> to test. 
/// </param> 
/// <returns> 
///  <c>true</c> when <paramref name="exception"/> can be suppressed; otherwise <c>false</c>. 
/// </returns> 
/// <remarks> 
/// <para> 
///  We do not want to suppress <see cref="OutOfMemoryException"/> or <see cref="ThreadAbortException"/> 
///  or any exception derived from them (except for <see cref="InsufficientMemoryException"/>, which we 
///  do allow suppression of). 
/// </para> 
/// <para> 
///  An exception that is otherwise suppressable is not considered suppressable when it has a nested 
///  non-suppressable exception. 
/// </para> 
/// </remarks> 
public static bool CanSuppress(this Exception exception) 
{ 
    foreach (var e in exception.DescendantsAndSelf()) 
    { 
     if ((e is OutOfMemoryException && !(e is InsufficientMemoryException)) || 
      e is ThreadAbortException) 
     { 
      return false; 
     } 
    } 

    return true; 
} 

private static IEnumerable<Exception> DescendantsAndSelf(this Exception exception) 
{ 
    if (exception != null) 
    { 
     yield return exception; 

     foreach (var child in exception.Children().SelectMany(ExceptionExtensions.DescendantsAndSelf)) 
     { 
      yield return child; 
     } 
    } 
} 

private static IEnumerable<Exception> Children(this Exception parent) 
{ 
    DebugAssumes.ArgNotNull(parent, nameof(parent)); 

    var aggregate = parent as AggregateException; 

    if (aggregate != null) 
    { 
     return aggregate.InnerExceptions; 
    } 
    else if (parent.InnerException != null) 
    { 
     return new[] { parent.InnerException }; 
    } 
    else 
    { 
     return ExceptionExtensions.EmptyArray; 
    } 
} 
+1

На первом снимке, на котором блоки не покрываются ..? – Rob

+0

Я предполагаю, что синий означает покрытый; Оранжевый означает не охваченный; Purpleish означает частично покрытый. Поэтому я бы сказал 'throw;' и '}', хотя 'throw' ** ** покрыт, а'} 'даже не должен содержать код ... –

+0

' '' не является чем-то «закрываемым», и я предполагаю, что он не подсвечен, потому что код никогда не выпадает из этой области (что хорошо - то же самое происходит после операторов возврата). Что касается броска, это не имеет ничего общего со структурой, потому что исключение является подавляемым, и задача не отменяется. Итак, вам нужно написать новый тест (с исходным кодом), который вызывает исключение, исключающее исключение, или отменить задачу – Rob

ответ

2

Хорошо - после того, как огромное количество копания - виновник await segment.GetStatusAsync(cancellationToken) , Это вызывает покрытие кода для просмотра throw как частично покрыто. Замена этой линии неасинхронным методом корректно показывает throw как покрытый

Теперь async создает внутреннюю машину состояния. Вы можете увидеть это в охвате кода, он находит метод, названный

<CreateIfNotExistsAsync>d__1.MoveNext:

Среди IL генерироваться, это выскочил ко мне:

IL_01B4: ldfld  MyExtensions+<CreateIfNotExistsAsync>d__1.<>s__1 
IL_01B9: isinst  System.Exception 
IL_01BE: stloc.s  0A 
IL_01C0: ldloc.s  0A 
IL_01C2: brtrue.s IL_01CB 
IL_01C4: ldarg.0  
IL_01C5: ldfld  MyExtensions+<CreateIfNotExistsAsync>d__1.<>s__1 
IL_01CA: throw  
IL_01CB: ldloc.s  0A 
IL_01CD: call  System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture 
IL_01D2: callvirt System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw 

Здесь есть два способа, исключение:

IL_01B4: ldfld  MyExtensions+<CreateIfNotExistsAsync>d__1.<>s__1 
IL_01B9: isinst  System.Exception 
IL_01BE: stloc.s  0A 
IL_01C0: ldloc.s  0A 
IL_01C2: brtrue.s IL_01CB 
IL_01C4: ldarg.0  
IL_01C5: ldfld  MyExtensions+<CreateIfNotExistsAsync>d__1.<>s__1 
IL_01CA: throw  

Это, по существу, принимая поле от s__1, и если это тип Exception. Например: machine.state1 is Exception

Тогда, если это правда, то ветви к IL_01CB

IL_01CB: ldloc.s  0A 
IL_01CD: call  System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture 
IL_01D2: callvirt System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw 

Который бросает исключение. Однако, если это false, он вызывает OpCode.

Это означает, что throw переведен на два возможных пути, только один из которых выполняется. Я не уверен, что возможно в C# для IL_01B9: isinst System.Exception когда-либо быть ложным, но я могу ошибаться - или это возможно.NET в целом.

Плохая новость заключается в том, что у меня нет для этого решения. Мой совет: используйте покрытие кода как руководство , так как даже покрытие на 100% не означает, что код не содержит ошибок. Сказав это, вы можете логически вывести throw, обозначенный как «частично покрытый», по существу, такой же, как и полностью покрытый.

+0

Спасибо за это! Я все еще надеюсь найти способ заставить Visual Studio сообщать код как полностью покрытый - так как это действительно так. –

+0

@ MårtenWikström Я могу рекомендовать только другой инструмент - например, 'dotCover' - который анализирует строки кода, а не исполнение IL. Я не уверен, настраивается ли встроенный охват таким образом. Любопытно, что IL * появляется для меня как избыточный, поскольку он ранее проверяет, что объект является исключением, хотя функциональность все еще остается даже при компиляции с оптимизацией – Rob

Смежные вопросы