gpt4 book ai didi

c# - 预期事件序列报告重复事件的测试助手

转载 作者:太空狗 更新时间:2023-10-29 20:23:48 25 4
gpt4 key购买 nike

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

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());
}

以及被测代码:

    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);
}
}

所以测试期望 FuelFilledFuelChanged 之前触发,但实际上 FuelChanged 先触发,这导致测试失败。

然而,我的测试报告说 FuelChanged 被触发了两次,但是当我单步执行代码时,很明显 FuelFilledFuelChanged< 之后被触发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 分配给两个事件,而不是使用不同的订阅/索引状态。有什么想法吗?

更新:尽管它们与我的实际代码有相似之处,但到目前为止我无法成功发布任何一个答案(与我的初始结果相同),所以我认为问题出在我的其他地方FuelTank 代码。我在下面粘贴了 FuelTank 的完整代码:

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);
}
}
}

FuelEventArgs 看起来像这样:

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;
}
}

FireEvent 扩展方法如下所示:

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);
}
}
}

完整的测试代码可以在上面的问题中找到,测试执行期间没有调用其他代码。

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

更新 2:

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

最佳答案

根据 Nick 的问题,我有以下实现。

首先是 FuelTank 类:

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);
}
}
}

由于缺少事件处理程序的代码,我假设使用它。正如前面代码块中的评论所述,使用普通委托(delegate)可以更轻松地完成。这只是一个选择问题,我认为这个实现还不是最好的,但足够适合调试:

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());
}
}

现在,当使用 NUnit 运行测试时,我注意到以下结果:

第一个被触发的事件是 FuelChanged 事件,这导致方法中的触发队列

public static void ExpectEventSequence(Queue<Action<EventHandler>> subscribeActions, Action triggerAction, Queue<int> expectedSequence)

包含 {1}。

下一个触发的事件是 FuelFull 事件,这意味着被触发的队列现在包含:{1,0} 根据 Nick 的问题预期。

触发的最后一个事件是 FuelNoLongerEmpty 事件,而这个事件未通过测试。

注意:
由于此代码尚未提供对 lambda 可能会造成一些干扰这一事实的原始问题的答案,正如我在上面提供的代码所做的那样。

以下规则适用于 lambda 表达式中的变量范围:

  • 一个被捕获的变量在引用它的委托(delegate)超出范围。
  • 在 lambda 表达式中引入的变量在
    中不可见外法。
  • lambda 表达式不能直接捕获 ref 或 out 参数
    从一个封闭的方法。
  • lambda 表达式中的 return 语句不会导致
    封闭方法返回。
  • lambda 表达式不能包含 goto 语句、break 语句、或 continue 语句,其目标在主体外或主体内一个包含的匿名函数。

所以 Nick 的原始问题中的问题可能是由于您枚举了一个队列而引起的。在枚举并将它们直接传递给 lambda 表达式时,您将使用引用。一个技巧可能是通过将它复制到迭代循环范围内的局部变量来实际取消引用它。这正是 smiech 在他的帖子中所指的内容。

编辑:

我刚刚为您重新调查了一遍。您确定您所面临的“挑战”不仅仅是将被解雇字典的索引与 expectedSequence.Dequeue 进行比较的事实以相反的顺序发生吗?请注意,队列是基于 FIFO 的,因此在出队时,它将检索第一个插入的...

我注意到(根据我的代码)被激发的字典包含 {1,0} 而 expectedSequence 字典包含 {0,1}。通过查看预期事件,这对 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);
}

然后您的测试中的所有内容都将完美通过,正如您在这张截图中看到的那样:

enter image description here

关于c# - 预期事件序列报告重复事件的测试助手,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27754947/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com