当大多数班级缺少覆盖时,代码覆盖率分析显示100%的覆盖率 [英] Code Coverage analysis shows 100% coverage when most of the class is missing coverage

查看:102
本文介绍了当大多数班级缺少覆盖时,代码覆盖率分析显示100%的覆盖率的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个实现IDisposable接口的类。

 使用系统; 
使用System.Threading;
使用System.Threading.Tasks;

///< summary>
///< para>
///引擎计时器允许启动计时器,该计时器将以给定的时间间隔执行回调。
///< / para>
///< para>
///计时器可能会触发:
///-以给定的间隔无限触发
///-触发一次
///-触发_n_次。
///< / para>
///< para>
///引擎计时器在处理后将自行停止。
///< / para>
///< para>
///计时器要求您提供一个实例,该实例将对其执行操作。
///将在每次触发间隔为回调实例提供通用实例。
///< / para>
///< para>
///在下面的示例中,为计时器提供了IPlayer的实例。
///在第一次触发回调之前,它会延迟30秒启动计时器。
///告诉计时器每60秒触发一次,触发次数为0。如果提供0,它将无限运行。
///最后,将为其提供回调,这将每60秒保存一次播放器。
/// @code
/// var timer = new EngineTimer< IPlayer>(new DefaultPlayer());
/// timer.StartAsync(30000,6000,0,(player,timer)=> player.Save());
/// @endcode
///< / para>
///< / summary>
///< typeparam name = T>在调用计时器回调时将提供的类型。
个公共密封类EngineTimer< T> :CancellationTokenSource,IDisposable
{
///< summary>
///计时器任务
///< / summary>
private Task timerTask;

///< summary>
///到目前为止,我们已经触发计时器多少次了。
///< / summary>
private long fireCount = 0;

///< summary>
///初始化< see cref = EngineTimer {T} />的新实例。类。
///< / summary>
///< param name = callback>回调。< / param>
///< param name = state>状态。< / param>
public EngineTimer(T state)
{
if(state == null)
{
throw new ArgumentNullException(nameof(state), EngineTimer构造函数需要一个非-null参数。);
}

this.StateData =状态;
}

///< summary>
///获取实例化时提供给计时器的对象。
///在触发时,将在每个时间间隔将此对象提供给回调。
///< / summary>
public T StateData {get;私人套装; }

///< summary>
///获取一个值,该值指示引擎计时器当前是否正在运行。
///< / summary>
public bool IsRunning {get;私人套装; }

///< summary>
///< para>
///启动计时器,在指定的每个间隔触发一个同步回调,直到达到`numberOfFires`为止。
///如果`numberOfFires`为0,则将无限期调用回调,直到手动停止计时器为止。
///< / para>
///< para>
///下面的示例显示如何启动计时器,并为其提供回调。
///< / para>
/// @code
/// var timer = new EngineTimer< IPlayer>(new DefaultPlayer());
/// double startDelay = TimeSpan.FromSeconds(30).TotalMilliseconds;
/// double interval = TimeSpan.FromMinutes(10).TotalMilliseconds;
/// int numberOfFires = 0;
///
/// timer.Start(
/// startDelay,
/// interval,
/// numberOfFires,
/ //(player,timer)=> player.Save());
/// @endcode
///< / summary>
///< param name = startDelay>
///< para>
///`startDelay`用于指定计时器第一次调用回调之前必须经过多少时间。
///如果提供0,则在启动计时器后将立即调用回调。
///< / para>
///< para>
///`startDelay`以毫秒为单位。
///< / para>
///< / param>
///< param name = interval>时间间隔,以毫秒为单位。< / param>
///< param name = numberOfFires>指定到达间隔时调用计时器回调的次数。 < / param>设置为0表示无限。
public void Start(double startDelay,double interval,int numberOfFires,Action< T,EngineTimer< T>>回调)
{
this.IsRunning = true;

this.timerTask =任务
.Delay(TimeSpan.FromMilliseconds(startDelay),this.Token)
.ContinueWith(
(task,state)=> RunTimer(任务,(Tuple< Action< T,EngineTimer< T>),状态,间隔,numberOfFires),
Tuple.Create(回调,this.StateData),
CancellationToken.None ,
TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion,
TaskScheduler.Default);,
}

///< summary>
///开始指定的启动延迟。
///< / summary>
///< param name = startDelay>启动延迟,以毫秒为单位。< / param>
///< param name = interval>时间间隔,以毫秒为单位。< / param>
///< param name = numberOfFires>指定到达间隔时调用计时器回调的次数。 < / param>设置为0表示无限。
public void StartAsync(double startDelay,double interval,int numberOfFires,Func< T,EngineTimer< T> ;, Task>回调))
{
this.IsRunning = true;

this.timerTask =任务
.Delay(TimeSpan.FromMilliseconds(startDelay),this.Token)
.ContinueWith(
async(task,state)=> ;等待RunTimerAsync(task,(Tuple< Func< T,EngineTimer< T> Task>)状态,间隔,numberOfFires),
Tuple.Create(回调,this.StateData),
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion,
TaskScheduler.Default);
}

///< summary>
///停止该实例的计时器。
///停止计时器不会丢弃EngineTimer,允许您在需要时重新启动计时器。
///< / summary>
public void Stop()
{
if(!this.IsCancellationRequested)
{
this.Cancel();
}
this.IsRunning = false;
}

///< summary>
///停止计时器并释放< see cref = T:System.Threading.CancellationTokenSource />使用的非托管资源。类,并有选择地释放托管资源。
///< / summary>
///< param name = disposed> true,以同时释放托管资源和非托管资源;如果为false,则仅释放非托管资源。< / param>
保护的覆盖无效void Dispose(布尔处理)
{
if(处理)
{
this.IsRunning = false;
this.Cancel();
}

base.Dispose(处置);
}

私有异步任务RunTimer(任务任务,Tuple< Action< T,EngineTimer< T>>,T>状态,双间隔,int numberOfFires)
{
while(!this.IsCancellationRequested)
{
//仅在应有的情况下增加。
if(numberOfFires> 0)
{
this.fireCount ++;
}

state.Item1(state.Item2,此);
等待PerformTimerCancellationCheck(interval,numberOfFires);
}
}

私有异步任务RunTimerAsync(任务任务,Tuple< Func< T,EngineTimer< T>,Task>,T>状态,双间隔,int numberOfFires)
{
while(!this.IsCancellationRequested)
{
//仅在应有的情况下递增。
if(numberOfFires> 0)
{
this.fireCount ++;
}

等待state.Item1(state.Item2,此);
等待PerformTimerCancellationCheck(interval,numberOfFires);
}
}

私有异步任务PerformTimerCancellationCheck(double interval,int numberOfFires)
{
//如果我们已达到火灾计数,请停止。如果设置为0,则触发直到手动停止。
if(numberOfFires> 0&& this.fireCount> = numberOfFires)
{
this.Stop();
}

等待Task.Delay(TimeSpan.FromMilliseconds(interval),this.Token).ConfigureAwait(false);
}
}

然后我为该类创建了一系列单元测试。

  [TestClass] 
公共类EngineTimerTests
{
[TestMethod]
[TestCategory( MudDesigner)]
[TestCategory( Engine)]
[TestCategory( Engine Core)]
[Owner( Johnathon Sullinger)]
[ExpectedException(typeof(ArgumentNullException))]
public void Exception_thrown_with_null_ctor_argument()
{
//操作
new EngineTimer< ComponentFixture>(null);
}

[TestMethod]
[TestCategory( MudDesigner)]
[TestCategory( Engine)]
[TestCategory( Engine Core )]
[Owner( Johnathon Sullinger)]
public void Ctor_sets_state_property()
{
//安排
var夹具=新ComponentFixture();

//操作
var engineTimer = new EngineTimer< ComponentFixture>(夹具);

//声明
Assert.IsNotNull(engineTimer.StateData,未从构造函数分配状态。);
Assert.AreEqual(fixture,engineTimer.StateData,为计时器分配了错误的State对象。);
}

[TestMethod]
[TestCategory( MudDesigner)]
[TestCategory( Engine)]
[TestCategory( Engine Core )]
[Owner( Johnathon Sullinger)]
public void Start_sets_is_running()
{
//安排
var Fixture = new ComponentFixture();
var engineTimer = new EngineTimer< ComponentFixture>(夹具);

//操作
engineTimer.Start(0,1,0,(component,timer)=> {});

//声明
Assert.IsTrue(engineTimer.IsRunning,引擎计时器未启动。);
}

[TestMethod]
[TestCategory( MudDesigner)]
[TestCategory( Engine)]
[TestCategory( Engine Core )]
[Owner( Johnathon Sullinger)]
public void Callback_invoked_when_running()
{
//安排
var夹具=新ComponentFixture();
var engineTimer = new EngineTimer< ComponentFixture>(夹具);
bool callbackInvoked = false;

//操作
engineTimer.Start(0,1,0,(component,timer)=> {callbackInvoked = true;});
Task.Delay(20);

//声明
Assert.IsTrue(callbackInvoked,引擎计时器未按预期调用回调。);
}
}

在Visual Studio中运行单元测试覆盖率分析时2015年,它告诉我班级100%都包含单元测试。但是,我只测试了构造函数和 Start()方法。没有任何单元测试可以触摸 Stop() StartAsync() Dispose() 方法。



为什么Visual Studio会告诉我我处于100%的代码覆盖率?





更新



我打开了重点报道,发现未覆盖 Stop()方法(如果我阅读了



有趣的是,该分析告诉我100%覆盖,即使覆盖范围内的高亮显示它未包含在任何单元测试路径中。

解决方案

解释代码覆盖范围的方式的最简单方法如下:


  1. 从目标目录中获取所有dll并进行构建基于 IL代码的有向图 G

  2. 使用 G 并创建所有可能的有向路径。

  3. 执行测试并标记相关路径。

  4. 计算百分比。

具有100%代码覆盖率的方法意味着您在 UT 的执行。(基本上,该工具不知道 UT 中正在测试的是哪个类)



根据上面的描述,您可能遇到的CC行为可能来自以下至少一种选择:


  1. 代码覆盖工具中的错误

  2. 当我的CC工具处理dll的精简版时,我遇到了类似的事情。(重建解决方案解决了这种情况下的问题)

  3. 至少有一个测试直接调用这些方法。

  4. 至少有一个测试间接调用了这些方法:通过继承,组合
    等。

要理解我为您提供服务的原因,



< a href = https://i.stack.imgur.com/3fKWj.jpg rel = nofollow noreferrer>



如果您想查看特定测试类别的覆盖率,则非常简单:


  1. 在测试资源管理器中,右键单击->分组依据->类

  2. 选择要监视的类。

  3. 右键单击->分析选定测试的代码覆盖率。


I have a class that implements the IDisposable interface.

using System;
using System.Threading;
using System.Threading.Tasks;

/// <summary>
/// <para>
/// The Engine Timer allows for starting a timer that will execute a callback at a given interval.
/// </para>
/// <para>
/// The timer may fire:
///  - infinitely at the given interval
///  - fire once
///  - fire _n_ number of times.
/// </para>
/// <para>
/// The Engine Timer will stop its self when it is disposed of.
/// </para>
/// <para>
/// The Timer requires you to provide it an instance that will have an operation performed against it.
/// The callback will be given the generic instance at each interval fired.
/// </para>
/// <para>
/// In the following example, the timer is given an instance of an IPlayer. 
/// It starts the timer off with a 30 second delay before firing the callback for the first time.
/// It tells the timer to fire every 60 seconds with 0 as the number of times to fire. When 0 is provided, it will run infinitely.
/// Lastly, it is given a callback, which will save the player every 60 seconds.
/// @code
/// var timer = new EngineTimer<IPlayer>(new DefaultPlayer());
/// timer.StartAsync(30000, 6000, 0, (player, timer) => player.Save());
/// @endcode
/// </para>
/// </summary>
/// <typeparam name="T">The type that will be provided when the timer callback is invoked.</typeparam>
public sealed class EngineTimer<T> : CancellationTokenSource, IDisposable
{
    /// <summary>
    /// The timer task
    /// </summary>
    private Task timerTask;

    /// <summary>
    /// How many times we have fired the timer thus far.
    /// </summary>
    private long fireCount = 0;

    /// <summary>
    /// Initializes a new instance of the <see cref="EngineTimer{T}"/> class.
    /// </summary>
    /// <param name="callback">The callback.</param>
    /// <param name="state">The state.</param>
    public EngineTimer(T state)
    {
        if (state == null)
        {
            throw new ArgumentNullException(nameof(state), "EngineTimer constructor requires a non-null argument.");
        }

        this.StateData = state;
    }

    /// <summary>
    /// Gets the object that was provided to the timer when it was instanced.
    /// This object will be provided to the callback at each interval when fired.
    /// </summary>
    public T StateData { get; private set; }

    /// <summary>
    /// Gets a value indicating whether the engine timer is currently running.
    /// </summary>
    public bool IsRunning { get; private set; }

    /// <summary>
    /// <para>
    /// Starts the timer, firing a synchronous callback at each interval specified until `numberOfFires` has been reached.
    /// If `numberOfFires` is 0, then the callback will be called indefinitely until the timer is manually stopped.
    /// </para>
    /// <para>
    /// The following example shows how to start a timer, providing it a callback.
    /// </para>
    /// @code
    /// var timer = new EngineTimer<IPlayer>(new DefaultPlayer());
    /// double startDelay = TimeSpan.FromSeconds(30).TotalMilliseconds;
    /// double interval = TimeSpan.FromMinutes(10).TotalMilliseconds;
    /// int numberOfFires = 0;
    /// 
    /// timer.Start(
    ///     startDelay, 
    ///     interval, 
    ///     numberOfFires, 
    ///     (player, timer) => player.Save());
    /// @endcode
    /// </summary>
    /// <param name="startDelay">
    /// <para>
    /// The `startDelay` is used to specify how much time must pass before the timer can invoke the callback for the first time.
    /// If 0 is provided, then the callback will be invoked immediately upon starting the timer.
    /// </para>
    /// <para>
    /// The `startDelay` is measured in milliseconds.
    /// </para>
    /// </param>
    /// <param name="interval">The interval in milliseconds.</param>
    /// <param name="numberOfFires">Specifies the number of times to invoke the timer callback when the interval is reached. Set to 0 for infinite.</param>
    public void Start(double startDelay, double interval, int numberOfFires, Action<T, EngineTimer<T>> callback)
    {
        this.IsRunning = true;

        this.timerTask = Task
            .Delay(TimeSpan.FromMilliseconds(startDelay), this.Token)
            .ContinueWith(
                (task, state) => RunTimer(task, (Tuple<Action<T, EngineTimer<T>>, T>)state, interval, numberOfFires),
                Tuple.Create(callback, this.StateData),
                CancellationToken.None,
                TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion,
                TaskScheduler.Default);
    }

    /// <summary>
    /// Starts the specified start delay.
    /// </summary>
    /// <param name="startDelay">The start delay in milliseconds.</param>
    /// <param name="interval">The interval in milliseconds.</param>
    /// <param name="numberOfFires">Specifies the number of times to invoke the timer callback when the interval is reached. Set to 0 for infinite.</param>
    public void StartAsync(double startDelay, double interval, int numberOfFires, Func<T, EngineTimer<T>, Task> callback)
    {
        this.IsRunning = true;

        this.timerTask = Task
            .Delay(TimeSpan.FromMilliseconds(startDelay), this.Token)
            .ContinueWith(
                async (task, state) => await RunTimerAsync(task, (Tuple<Func<T, EngineTimer<T>, Task>, T>)state, interval, numberOfFires),
                Tuple.Create(callback, this.StateData),
                CancellationToken.None,
                TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion,
                TaskScheduler.Default);
    }

    /// <summary>
    /// Stops the timer for this instance.
    /// Stopping the timer will not dispose of the EngineTimer, allowing you to restart the timer if you need to.
    /// </summary>
    public void Stop()
    {
        if (!this.IsCancellationRequested)
        {
            this.Cancel();
        } 
        this.IsRunning = false;
    }

    /// <summary>
    /// Stops the timer and releases the unmanaged resources used by the <see cref="T:System.Threading.CancellationTokenSource" /> class and optionally releases the managed resources.
    /// </summary>
    /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            this.IsRunning = false;
            this.Cancel();
        }

        base.Dispose(disposing);
    }

    private async Task RunTimer(Task task, Tuple<Action<T, EngineTimer<T>>, T> state, double interval, int numberOfFires)
    {
        while (!this.IsCancellationRequested)
        {
            // Only increment if we are supposed to.
            if (numberOfFires > 0)
            {
                this.fireCount++;
            }

            state.Item1(state.Item2, this);
            await PerformTimerCancellationCheck(interval, numberOfFires);
        }
    }

    private async Task RunTimerAsync(Task task, Tuple<Func<T, EngineTimer<T>, Task>, T> state, double interval, int numberOfFires)
    {
        while (!this.IsCancellationRequested)
        {
            // Only increment if we are supposed to.
            if (numberOfFires > 0)
            {
                this.fireCount++;
            }

            await state.Item1(state.Item2, this);
            await PerformTimerCancellationCheck(interval, numberOfFires);
        }
    }

    private async Task PerformTimerCancellationCheck(double interval, int numberOfFires)
    {
        // If we have reached our fire count, stop. If set to 0 then we fire until manually stopped.
        if (numberOfFires > 0 && this.fireCount >= numberOfFires)
        {
            this.Stop();
        }

        await Task.Delay(TimeSpan.FromMilliseconds(interval), this.Token).ConfigureAwait(false);
    }
}

I then created a series of unit tests for the class.

[TestClass]
public class EngineTimerTests
{
    [TestMethod]
    [TestCategory("MudDesigner")]
    [TestCategory("Engine")]
    [TestCategory("Engine Core")]
    [Owner("Johnathon Sullinger")]
    [ExpectedException(typeof(ArgumentNullException))]
    public void Exception_thrown_with_null_ctor_argument()
    {
        // Act
        new EngineTimer<ComponentFixture>(null);
    }

    [TestMethod]
    [TestCategory("MudDesigner")]
    [TestCategory("Engine")]
    [TestCategory("Engine Core")]
    [Owner("Johnathon Sullinger")]
    public void Ctor_sets_state_property()
    {
        // Arrange
        var fixture = new ComponentFixture();

        // Act
        var engineTimer = new EngineTimer<ComponentFixture>(fixture);

        // Assert
        Assert.IsNotNull(engineTimer.StateData, "State was not assigned from the constructor.");
        Assert.AreEqual(fixture, engineTimer.StateData, "An incorrect State object was assigned to the timer.");
    }

    [TestMethod]
    [TestCategory("MudDesigner")]
    [TestCategory("Engine")]
    [TestCategory("Engine Core")]
    [Owner("Johnathon Sullinger")]
    public void Start_sets_is_running()
    {
        // Arrange
        var fixture = new ComponentFixture();
        var engineTimer = new EngineTimer<ComponentFixture>(fixture);

        // Act
        engineTimer.Start(0, 1, 0, (component, timer) => { });

        // Assert
        Assert.IsTrue(engineTimer.IsRunning, "Engine Timer was not started.");
    }

    [TestMethod]
    [TestCategory("MudDesigner")]
    [TestCategory("Engine")]
    [TestCategory("Engine Core")]
    [Owner("Johnathon Sullinger")]
    public void Callback_invoked_when_running()
    {
        // Arrange
        var fixture = new ComponentFixture();
        var engineTimer = new EngineTimer<ComponentFixture>(fixture);
        bool callbackInvoked = false;

        // Act
        engineTimer.Start(0, 1, 0, (component, timer) => { callbackInvoked = true; });
        Task.Delay(20);

        // Assert
        Assert.IsTrue(callbackInvoked, "Engine Timer did not invoke the callback as expected.");
    }
}

When I run the unit test coverage analysis in Visual Studio 2015, it tells me that the class is 100% covered by unit tests. However, I've only tested the constructor and the Start() method. None of the unit tests touch the Stop(), StartAsync() or Dispose() methods.

Why would Visual Studio tell me I am at 100% code coverage?

Update

I turned on coverage highlights and discovered that the Stop() method is not covered (if I read this right).

It is interesting that the analysis tells me it's 100% covered, even though the coverage highlights shows that it's not included in any unit test paths.

解决方案

The simplest way to explain the way code coverage works is as the following:

  1. Take all dlls from the target directory and build a directed graph G based on the IL code.
  2. Use G and creates all possible directed-paths.
  3. Execute the tests and mark the relevant paths.
  4. Calculate the percentage.

Methods with 100% code coverage means you walk through all the method's paths, during your UT's execution.(Basically the tool doesn't know which class in under test in the UT)

Based on the above description the CC's behavior you've faced could happened from at least one of the following options:

  1. Bug in the code coverage tool
  2. I've faced something similar when my CC tool worked on deffrent version of my dlls.(Rebuild Solution solved the problem in this case)
  3. At least one of your tests calls those methods directly.
  4. At least one of your tests calls those methods indirectly: Through inheritance, composition and etc...

To understand the reason I offer you to follow:

If you want to see the coverage from specific test class it's very easy:

  1. In "Test Explorer" right click -> Group By -> Class
  2. Select the class you want to monitor.
  3. Right click -> Analyze Code Coverage For Selected Tests.

这篇关于当大多数班级缺少覆盖时,代码覆盖率分析显示100%的覆盖率的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆