Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/319.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 预期事件序列报告重复事件的测试帮助程序_C#_Unit Testing_Events_Lambda_Anonymous Function - Fatal编程技术网

C# 预期事件序列报告重复事件的测试帮助程序

C# 预期事件序列报告重复事件的测试帮助程序,c#,unit-testing,events,lambda,anonymous-function,C#,Unit Testing,Events,Lambda,Anonymous Function,我的单元测试有一个helper方法,它断言特定的事件序列是按照特定的顺序引发的。代码如下: public static void ExpectEventSequence(Queue<Action<EventHandler>> subscribeActions, Action triggerAction) { var expectedSequence = new Queue<int>(); for (int i = 0; i < subsc

我的单元测试有一个helper方法,它断言特定的事件序列是按照特定的顺序引发的。代码如下:

public static void ExpectEventSequence(Queue<Action<EventHandler>> subscribeActions, Action triggerAction)
{
    var expectedSequence = new Queue<int>();
    for (int i = 0; i < subscribeActions.Count; i++)
    {
        expectedSequence.Enqueue(i);
    }

    ExpectEventSequence(subscribeActions, triggerAction, expectedSequence);
}

    public static void ExpectEventSequence(Queue<Action<EventHandler>> subscribeActions, Action triggerAction, Queue<int> expectedSequence)
    {
        var fired = new Queue<int>();
        var actionsCount = subscribeActions.Count;

        for(var i =0; i< actionsCount;i++)
        {
            subscription((o, e) =>
                {
                    fired.Enqueue(i);
                });
        }

        triggerAction();

        var executionIndex = 0;

        var inOrder = true;

        foreach (var firedIndex in fired)
        {

            if (firedIndex != expectedSequence.Dequeue())
            {
                inOrder = false;
                break;
            }

            executionIndex++;
        }

        if (subscribeActions.Count != fired.Count)
        {
            Assert.Fail("Not all events were fired.");
        }

        if (!inOrder)
        {
            Assert.Fail(string.Format(
                CultureInfo.CurrentCulture,
                "Events were not fired in the expected sequence from element {0}",
                executionIndex));
        }

    }
    [Test()]
    public void FillFuel_Test([Values(1, 5, 10, 100)]float maxFuel)
    {
        var fuelTank = new FuelTank()
        {
            MaxFuel = maxFuel
        };

        var eventHandlerSequence = new Queue<Action<EventHandler>>();

        eventHandlerSequence.Enqueue(x => fuelTank.FuelFull += x);

        //Dealing with a subclass of EventHandler
        eventHandlerSequence.Enqueue(x => fuelTank.FuelChanged += (o, e) => x(o, e));

        Test.ExpectEventSequence(eventHandlerSequence, () => fuelTank.FillFuel());
    }
因此,测试预期在
FuelChanged
之前启动
FuelFilled
,但实际上
FuelChanged
首先启动,这导致测试失败

然而,我的测试报告了
FuelChanged
被激发了两次,但是当我逐步查看代码时,很明显
FuelFilled
FuelChanged
之后被激发,而
FuelChanged
只被激发了一次

我假设这与lambda处理局部状态的方式有关,可能for循环迭代器变量只设置为最终值,因此我用以下内容替换for循环:

        var subscriptions = subscribeActions.ToList();

        foreach (var subscription in subscriptions)
        {
            subscription((o, e) =>
                {
                    var index = subscriptions.IndexOf(subscription);
                    fired.Enqueue(index);
                });
        }
但是结果是相同的,fired包含{1;1},而不是{1;0}

现在我想知道是否将相同的lambda分配给这两个事件,而不是使用不同的订阅/索引状态。有什么想法吗

更新:尽管这两个答案与我的实际代码相似,但到目前为止,我无法成功发布这两个答案(与我的初始结果相同),因此我假设问题位于我的
燃油箱
代码的其他地方。我已将
燃油箱的完整代码粘贴到下面:

public class FuelTank
{
    public FuelTank()
    {

    }

    public FuelTank(float initialFuel, float maxFuel)
    {
        MaxFuel = maxFuel;
        Fuel = initialFuel;
    }

    public float Fuel
    {
        get
        {
            return fuel;
        }
        private set
        {
            var adjustedFuel = Math.Max(0, Math.Min(value, MaxFuel));

            if (fuel != adjustedFuel)
            {
                var oldFuel = fuel;

                fuel = adjustedFuel;

                RaiseCheckFuelChangedEvents(oldFuel);
            }
        }
    }

    private float maxFuel;

    public float MaxFuel
    {
        get
        {
            return maxFuel;
        }
        set
        {
            if (value < 0)
            {
                throw new ArgumentOutOfRangeException("MaxFuel", value, "Argument must be not be less than 0.");
            }
            maxFuel = value;
        }
    }

    private float fuel;

    public event EventHandler<FuelEventArgs> FuelChanged;

    public event EventHandler FuelEmpty;

    public event EventHandler FuelFull;

    public event EventHandler FuelNoLongerEmpty;

    public event EventHandler FuelNoLongerFull;

    public void AddFuel(float fuel)
    {
        Fuel += fuel;
    }

    public void ClearFuel()
    {
        Fuel = 0;
    }

    public void DrainFuel(float fuel)
    {
        Fuel -= fuel;
    }

    public void FillFuel()
    {
        Fuel = MaxFuel;
    }

    private void RaiseCheckFuelChangedEvents(float oldFuel)
    {
        FuelChanged.FireEvent(this, new FuelEventArgs(oldFuel, Fuel));

        if (fuel == 0)
        {
            FuelEmpty.FireEvent(this, EventArgs.Empty);
        }
        else if (fuel == MaxFuel)
        {
            FuelFull.FireEvent(this, EventArgs.Empty);
        }

        if (oldFuel == 0 && Fuel != 0)
        {
            FuelNoLongerEmpty.FireEvent(this, EventArgs.Empty);
        }
        else if (oldFuel == MaxFuel && Fuel != MaxFuel)
        {
            FuelNoLongerFull.FireEvent(this, EventArgs.Empty);
        }
    }
}
FireEvent
扩展方法如下所示:

public class FuelEventArgs : EventArgs
{
    public float NewFuel
    {
        get;
        private set;
    }

    public float OldFuel
    {
        get;
        private set;
    }

    public FuelEventArgs(float oldFuel, float newFuel)
    {
        this.OldFuel = oldFuel;
        this.NewFuel = newFuel;
    }
}
public static class EventHandlerExtensions
{
    /// <summary>
    /// Fires the event. This method is thread safe.
    /// </summary>
    /// <param name="handler"> The handler. </param>
    /// <param name="sender">  Source of the event. </param>
    /// <param name="args">    The <see cref="EventArgs"/> instance containing the event data. </param>
    public static void FireEvent(this EventHandler handler, object sender, EventArgs args)
    {
        var handlerCopy = handler;

        if (handlerCopy != null)
        {
            handlerCopy(sender, args);
        }
    }

    /// <summary>
    /// Fires the event. This method is thread safe.
    /// </summary>
    /// <typeparam name="T"> The type of event args this handler has. </typeparam>
    /// <param name="handler"> The handler. </param>
    /// <param name="sender"> Source of the event. </param>
    /// <param name="args"> The <see cref="EventArgs"/> instance containing the event data. </param>
    public static void FireEvent<T>(this EventHandler<T> handler, object sender, T args) where T : EventArgs
    {
        var handlerCopy = handler;

        if (handlerCopy != null)
        {
            handlerCopy(sender, args);
        }
    }
}
公共静态类EventHandlerExtensions
{
/// 
///激发事件。此方法是线程安全的。
/// 
///处理者。
///事件的来源。
///包含事件数据的实例。
公共静态void FireEvent(此EventHandler处理程序、对象发送者、EventArgs args)
{
var handlerCopy=handler;
如果(handlerCopy!=null)
{
handlerCopy(发送方,args);
}
}
/// 
///激发事件。此方法是线程安全的。
/// 
///此处理程序具有的事件参数的类型。
///处理者。
///事件的来源。
///包含事件数据的实例。
公共静态void FireEvent(此EventHandler处理程序、对象发送方、T args),其中T:EventArgs
{
var handlerCopy=handler;
如果(handlerCopy!=null)
{
handlerCopy(发送方,args);
}
}
}
完整的测试代码可以在上面的问题中找到,在测试执行期间没有调用其他代码

我通过Unity3D引擎的Unity测试工具插件、.NET 3.5版(我相信它更接近Mono 2.0)和Visual Studio 2013使用NUnit测试框架

更新2:


在将代码和测试提取到他们自己的项目(Unity3D生态系统之外)后,所有测试都按预期运行,因此我将不得不将这一测试归因于Unity->Visual Studio bridge中的一个错误。

第一部分:是的,它与lambdas变量作用域的方式有关。看见 因为我花了一些时间试图弄明白,所以我允许自己粘贴我使用过的代码(所有测试都通过)

类测试
{
公共静态void ExpectEventSequence(队列订阅动作、动作触发动作)
{
var expectedSequence=新队列();
for(int i=0;i
{
var index=订阅。IndexOf(订阅);
已激发。排队(索引);
});
}
触发作用();
var executionIndex=0;
var-inoorder=true;
foreach(点火时的var firedIndex)
{
if(firedIndex!=expectedSequence.Dequeue())
{
顺序=假;
打破
}
executionIndex++;
}
if(subscribeActions.Count!=fired.Count)
Assert.Fail(“并非所有事件都已触发”);
if(!inoder)
断言
.Fail(string.Format)(
CultureInfo.CurrentCulture,
“未按元素{0}的预期顺序激发事件”,
执行指数);
}
}
公共类燃料
{
public event EventHandler FuelChanged=委托{};
public event EventHandler FuelEmpty=委托{};
public event EventHandler FuelFull=委托{};
public event EventHandler FuelNoLongerFull=委托{};
public event EventHandler FuelNoLongerEmpty=委托{};
私人浮油;
公共浮油
{
获取{返回燃料;}
专用设备
{
var调整燃料=数学最大值(0,数学最小值(值,最大燃料));
如果(燃油!=调整燃油)
{
var oldFuel=燃料;
燃料=调整后的燃料;
提高燃料变化率(旧燃料);
}
}
}
公共燃料
{
燃料=最大燃料;
}
公共浮点MaxFuel{get;set;}
私有无效提升燃料变更(浮动旧燃料)
{
FuelChanged(此,新的FuelEventArgs(旧燃料,燃料));
如果(燃油==0)
FuelEmpty(这是EventArgs.Empty);
否则如果(燃料==最大燃料)
FuelFull(此为EventArgs.Empty);
如果(oldFuel==0&&Fuel!=0)
FuelNoLongerEmpty(此为EventArgs.Empty);
否则如果(oldFuel==MaxFuel&&Fuel!=MaxFuel)
FuelNoLongerFull(此为EventArgs.Empty);
}
}
公共类FuelEventArgs:EventArgs
{
公共燃油事件参数(浮动旧燃油、浮动燃油)
public static class EventHandlerExtensions
{
    /// <summary>
    /// Fires the event. This method is thread safe.
    /// </summary>
    /// <param name="handler"> The handler. </param>
    /// <param name="sender">  Source of the event. </param>
    /// <param name="args">    The <see cref="EventArgs"/> instance containing the event data. </param>
    public static void FireEvent(this EventHandler handler, object sender, EventArgs args)
    {
        var handlerCopy = handler;

        if (handlerCopy != null)
        {
            handlerCopy(sender, args);
        }
    }

    /// <summary>
    /// Fires the event. This method is thread safe.
    /// </summary>
    /// <typeparam name="T"> The type of event args this handler has. </typeparam>
    /// <param name="handler"> The handler. </param>
    /// <param name="sender"> Source of the event. </param>
    /// <param name="args"> The <see cref="EventArgs"/> instance containing the event data. </param>
    public static void FireEvent<T>(this EventHandler<T> handler, object sender, T args) where T : EventArgs
    {
        var handlerCopy = handler;

        if (handlerCopy != null)
        {
            handlerCopy(sender, args);
        }
    }
}
class Test
{
    public static void ExpectEventSequence(Queue<Action<EventHandler>> subscribeActions, Action triggerAction)
    {
        var expectedSequence = new Queue<int>();
        for (int i = 0; i < subscribeActions.Count; i++)
            expectedSequence.Enqueue(i);
        ExpectEventSequence(subscribeActions, triggerAction, expectedSequence);
    }

    public static void ExpectEventSequence(Queue<Action<EventHandler>> subscribeActions, Action triggerAction, Queue<int> expectedSequence)
    {
        var fired = new Queue<int>();
        var subscriptions = subscribeActions.ToList();

        foreach (var subscription in subscriptions)
        {
            subscription((o, e) =>
            {
                var index = subscriptions.IndexOf(subscription);
                fired.Enqueue(index);
            });
        }
        triggerAction();
        var executionIndex = 0;
        var inOrder = true;
        foreach (var firedIndex in fired)
        {
            if (firedIndex != expectedSequence.Dequeue())
            {
                inOrder = false;
                break;
            }
            executionIndex++;
        }
        if (subscribeActions.Count != fired.Count)
            Assert.Fail("Not all events were fired.");
        if (!inOrder)
            Assert
                .Fail(string.Format(
                CultureInfo.CurrentCulture,
                "Events were not fired in the expected sequence from element {0}",
                executionIndex));
    }
}

public class Fueled
{
    public event EventHandler<FuelEventArgs> FuelChanged = delegate { };
    public event EventHandler FuelEmpty = delegate { };
    public event EventHandler FuelFull = delegate { };
    public event EventHandler FuelNoLongerFull = delegate { };
    public event EventHandler FuelNoLongerEmpty = delegate { };
    private float fuel;

    public float Fuel
    {
        get{ return fuel; }
        private set
        {
            var adjustedFuel = Math.Max(0, Math.Min(value, MaxFuel));

            if (fuel != adjustedFuel)
            {
                var oldFuel = fuel;
                fuel = adjustedFuel;
                RaiseCheckFuelChangedEvents(oldFuel);
            }
        }
    }

    public void FillFuel()
    {
        Fuel = MaxFuel;
    }

    public float MaxFuel { get; set; }

    private void RaiseCheckFuelChangedEvents(float oldFuel)
    {
        FuelChanged(this, new FuelEventArgs(oldFuel, Fuel));

        if (fuel == 0)
            FuelEmpty(this, EventArgs.Empty);
        else if (fuel == MaxFuel)
            FuelFull(this, EventArgs.Empty);
        if (oldFuel == 0 && Fuel != 0)
            FuelNoLongerEmpty(this, EventArgs.Empty);
        else if (oldFuel == MaxFuel && Fuel != MaxFuel)
            FuelNoLongerFull(this, EventArgs.Empty);
    }
}

public class FuelEventArgs : EventArgs
{
    public FuelEventArgs(float oldFuel, float fuel)
    {
    }
}

[TestFixture]
public class Tests
{
    [Test()]
    public void FillFuel_Test([Values(1, 5, 10, 100)]float maxFuel)
    {
        var fuelTank = new Fueled()
        {
            MaxFuel = maxFuel
        };
        var eventHandlerSequence = new Queue<Action<EventHandler>>();
        //Dealing with a subclass of EventHandler
        eventHandlerSequence.Enqueue(x => fuelTank.FuelChanged += (o, e) => x(o, e));
        eventHandlerSequence.Enqueue(x => fuelTank.FuelFull += x);
        Test.ExpectEventSequence(eventHandlerSequence, () => fuelTank.FillFuel());
    }
}
[Test()]
public void FillFuel_Test([Values(1, 5, 10, 100)]float maxFuel)
{
    var fuelTank = new Fueled()
    {
        MaxFuel = maxFuel
    };
    var expectedEventSequence = new[]
    {
        "FuelChanged",
        "FuelFull"
    };
    var triggeredEventSequence = new List<string>();
    fuelTank.FuelChanged += (o, e) => triggeredEventSequence.Add("FuelChanged");
    fuelTank.FuelFull += (o, e) => triggeredEventSequence.Add("FuelFull");

    fuelTank.FillFuel();

    Assert.AreEqual(expectedEventSequence,triggeredEventSequence);
}
public class FuelTank
{
    private float fuel;

    //Basic classes for the event handling, could be done by providing a few simple delegates,
    //but this is just to stick as close to the original question as possible.
    public FuelChanged FuelChanged = new FuelChanged();
    public FuelEmpty FuelEmpty = new FuelEmpty();
    public FuelFull FuelFull = new FuelFull();
    public FuelNoLongerEmpty FuelNoLongerEmpty = new FuelNoLongerEmpty();
    public FuelNoLongerFull FuelNoLongerFull = new FuelNoLongerFull();


    public float MaxFuel { get; set; }

    public float Fuel
    {
        get
        {
            return fuel;
        }
        private set
        {
            var adjustedFuel = Math.Max(0, Math.Min(value, MaxFuel));

            if (fuel != adjustedFuel)
            {
                var oldFuel = fuel;

                fuel = adjustedFuel;

                RaiseCheckFuelChangedEvents(oldFuel);
            }
        }
    }

    public void FillFuel()
    {
        Fuel = MaxFuel;
    }

    private void RaiseCheckFuelChangedEvents(float oldFuel)
    {
        FuelChanged.FireEvent(this, new FuelEventArgs(oldFuel, Fuel));

        if (fuel == 0)
        {
            FuelEmpty.FireEvent(this, EventArgs.Empty);
        }
        else if (fuel == MaxFuel)
        {
            FuelFull.FireEvent(this, EventArgs.Empty);
        }

        if (oldFuel == 0 && Fuel != 0)
        {
            FuelNoLongerEmpty.FireEvent(this, EventArgs.Empty);
        }
        else if (oldFuel == MaxFuel && Fuel != MaxFuel)
        {
            FuelNoLongerFull.FireEvent(this, EventArgs.Empty);
        }
    }      
}
public class FuelEventArgs : EventArgs
{
    private float oldFuel, newFuel;

    public FuelEventArgs(float oldFuel, float newFuel)
    {
        this.oldFuel = oldFuel;
        this.newFuel = newFuel;
    }
}

public class FuelEvents
{      
    public event EventHandler FireEventHandler;

    public virtual void FireEvent(object sender, EventArgs fuelArgs)
    {
        EventHandler handler = FireEventHandler;
        if (null != handler)
            handler(this, fuelArgs);
    }

}

public class FuelChanged : FuelEvents
{             

    public override void FireEvent(object sender, EventArgs fuelArgs)
    {
        Console.WriteLine("Fired FuelChanged");
        base.FireEvent(sender, fuelArgs);
    }
}

public class FuelEmpty : FuelEvents
{
    public override void FireEvent(object sender, EventArgs fuelArgs)
    {
        Console.WriteLine("Fired FuelEmpty");
        base.FireEvent(sender, fuelArgs);
    }
}

public class FuelFull : FuelEvents
{
    public override void FireEvent(object sender, EventArgs fuelArgs)
    {
        Console.WriteLine("Fired FuelFull");
        base.FireEvent(sender, fuelArgs);
    }
}

public class FuelNoLongerEmpty : FuelEvents
{
    public override void FireEvent(object sender, EventArgs fuelArgs)
    {
        Console.WriteLine("Fired FuelNoLongerEmpty");
        base.FireEvent(sender, fuelArgs);
    }
}

public class FuelNoLongerFull : FuelEvents
{
    public override void FireEvent(object sender, EventArgs fuelArgs)
    {
        Console.WriteLine("Fired FuelNoLongerFull");
        base.FireEvent(sender, fuelArgs);
    }
}
[TestFixture]
public class Tests
{
    public static void ExpectEventSequence(Queue<Action<EventHandler>> subscribeActions, Action triggerAction)
    {
        var expectedSequence = new Queue<int>();
        for (int i = 0; i < subscribeActions.Count; i++)
        {
            expectedSequence.Enqueue(i);
        }

        ExpectEventSequence(subscribeActions, triggerAction, expectedSequence);
    }

    public static void ExpectEventSequence(Queue<Action<EventHandler>> subscribeActions, Action triggerAction, Queue<int> expectedSequence)
    {
        var fired = new Queue<int>();
        var actionsCount = subscribeActions.Count;

        //This code has been commented out due to the fact that subscription is unknown here.
        //I stuck to use the last solution that Nick provided himself

        //for (var i = 0; i < actionsCount; i++)
        //{
        //    subscription((o, e) =>
        //    {
        //        fired.Enqueue(i);
        //    });
        //}

        var subscriptions = subscribeActions.ToList();

        foreach (var subscription in subscriptions)
        {
            subscription((o, e) =>
            {
                var index = subscriptions.IndexOf(subscription);
                Console.WriteLine("[ExpectEventSequence] Found index: {0}", index);
                fired.Enqueue(index);
            });
        }

        triggerAction();

        var executionIndex = 0;

        var inOrder = true;

        foreach (var firedIndex in fired)
        {

            if (firedIndex != expectedSequence.Dequeue())
            {
                inOrder = false;
                break;
            }

            executionIndex++;
            Console.WriteLine("Execution index: {0}", executionIndex);
        }

        if (subscribeActions.Count != fired.Count)
        {
            Assert.Fail("Not all events were fired.");
        }

        if (!inOrder)
        {
            Console.WriteLine("Contents of Fired Queue: {0}", PrintValues(fired));

            Assert.Fail(string.Format(
                CultureInfo.CurrentCulture,
                "Events were not fired in the expected sequence from element {0}",
                executionIndex));

        }
    }

    private static string PrintValues(Queue<int> myCollection)
    {
        return string.Format( "{{0}}", string.Join(",", myCollection.ToArray()));

    }


    [Test()]
    [ExpectedException(typeof(DivideByZeroException))]
    public void FillFuel_Test([Values(1, 5, 10, 100)]float maxFuel)
    {  

        var fuelTank = new FuelTank()
        {
            MaxFuel = maxFuel
        };

        var eventHandlerSequence = new Queue<Action<EventHandler>>();

        eventHandlerSequence.Enqueue(x => fuelTank.FuelFull.FireEventHandler += x);

        //Dealing with a subclass of EventHandler
        eventHandlerSequence.Enqueue(x => fuelTank.FuelChanged.FireEventHandler += (o, e) => x(o, e));

        ExpectEventSequence(eventHandlerSequence, () => fuelTank.FillFuel());
    }
}
public static void ExpectEventSequence(Queue<Action<EventHandler>> subscribeActions, Action triggerAction, Queue<int> expectedSequence)
 public static void ExpectEventSequence(Queue<Action<EventHandler>> subscribeActions, Action triggerAction, Queue<int> expectedSequence)
  var subscriptions = subscribeActions.ToList();

  foreach (var firedIndex in fired)
  {

    if (firedIndex != expectedSequence.Dequeue())
    {
       inOrder = false;
       break;
    }

    executionIndex++;
    Console.WriteLine("Execution index: {0}", executionIndex);
  }
   //When comparing indexes, you'll probably need to reverse the fired queue
   fired = new Queue<int>(fired.Reverse());
   foreach (var firedIndex in fired)
   {

     if (firedIndex != expectedSequence.Dequeue())
     {
       inOrder = false;
       break;
     }

     executionIndex++;
     Console.WriteLine("Execution index: {0}", executionIndex);
   }