- c - 在位数组中找到第一个零
- linux - Unix 显示有关匹配两种模式之一的文件的信息
- 正则表达式替换多个文件
- linux - 隐藏来自 xtrace 的命令
我的单元测试有一个辅助方法,它断言特定事件序列是按特定顺序引发的。代码如下:
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);
}
}
所以测试期望 FuelFilled
在 FuelChanged
之前触发,但实际上 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 分配给两个事件,而不是使用不同的订阅/索引状态。有什么想法吗?
更新:尽管它们与我的实际代码有相似之处,但到目前为止我无法成功发布任何一个答案(与我的初始结果相同),所以我认为问题出在我的其他地方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 表达式中的变量范围:
所以 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);
}
然后您的测试中的所有内容都将完美通过,正如您在这张截图中看到的那样:
关于c# - 预期事件序列报告重复事件的测试助手,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27754947/
我正在阅读 Python 文档以真正深入了解 Python 语言,并遇到了 filter 和 map 函数。我以前使用过过滤器,但从未使用过映射,尽管我在 SO 上的各种 Python 问题中都见过这
当我尝试打印 BST 的级别顺序时,这个问题提示了我。 这是一个 Pre-Order Sequence: 4, 1, 2, 3, 5, 6, 7, 8 In_order Sequence : 1, 2
我的代码在 main(序列测试;)的第一行出现错误,指出它是对 sequence::sequence() 的 undefined reference 。我无法更改 main 中的代码。有谁知道我该如何
这可能很简单,但我在通常的 latex 指南中找不到任何相关内容。在这句话中: {\em hello\/} “\/”的目的是什么? 最佳答案 这就是所谓的斜体校正。其目的是确保斜体文本后有适当的间距。
当我从 Postgresql 表中删除所有记录,然后尝试重置序列以在插入时开始一个编号为 1 的新记录时,我得到不同的结果: SELECT setval('tblname_id_seq', (SELE
在版本10.0.3中,MariaDB引入了一种称为序列的存储引擎。 其ad hoc为操作生成整数序列,然后终止。 该序列包含正整数,以降序或升序排列,并使用起始,结束和递增值。 它不允许在多个查询中
如何在 Groovy 中获取给定数字的序列,例如: def number = 169 // need a method in groovy to find the consecutive number
基本上,如果这是 .NET,它看起来像这样: ISomething { string A { get; } int B { get; } } var somethings = new List
说以下代码部分(同一块): A <= 1 A <= 2 变量 A 总是被赋值为 2 吗?还是会出现竞争条件并分配 1 或 2? 我对非阻塞赋值的理解是,由硬件在 future 分配变量 A,因此它可能
在运行 WiX 设置时,我正在寻找操作列表及其顺序。不知何故,官方网站似乎没有提供任何信息。 基本问题是我想正确安排我的自定义操作。通常我需要使用 regsvr32.exe 注册一个 DLL,而这只能
F#初学者在这里 我想创建一个类型,它是具有至少一个元素的另一种具体类型(事件)的序列。任何其他元素都可以在以后随时添加。通常在 C# 中,我会创建一个具有私有(private) List 和公共(p
作为构建过程和不断发展的数据库的一部分,我试图创建一个脚本,该脚本将删除用户的所有表和序列。我不想重新创建用户,因为这将需要比所允许的更多的权限。 我的脚本创建了一个过程来删除表/序列,执行该过程,然
我想恢复两个向量的第一个日期和相同向量的第二个日期之间的日期序列,.... 这是一个例子: dates1 = as.Date(c('2015-10-01', '2015-03-27', '2015-0
这个问题已经有答案了: sql ORDER BY multiple values in specific order? (12 个回答) 已关闭 9 年前。 我有一个 sql 语句,我想要ORDER
我想恢复两个向量的第一个日期和相同向量的第二个日期之间的日期序列,.... 这是一个例子: dates1 = as.Date(c('2015-10-01', '2015-03-27', '2015-0
在用java编写代码时,我需要用“],[”分割字符串。下面是我的代码。 try (BufferedReader reader = new BufferedReader(new InputStreamR
这个问题已经有答案了: Project Euler Question 14 (Collatz Problem) (8 个回答) 已关闭 9 年前。 我正在尝试查找数字的 Collatz 序列。以下
我有一个例程函数process_letter_location(const char& c, string &word)。 在我的 main 中,我声明了一系列字符串变量,如下所示: string s
我需要找到最长的多米诺骨牌链,给定一组 12 个随机挑选的多米诺骨牌。我已经递归地生成了多米诺骨牌的所有可能性(使用 0 到 12 的面值有 91 种可能性)。多米诺骨牌由一 block “砖 blo
我有这个数据结构 Seq,它继承了类 vector 但有一些额外的功能。使用这个数据结构 Seq 我有这个预定义的数据结构: typedef Seq > MxInt2d; 我现在想要一个包含多个 Mx
我是一名优秀的程序员,十分优秀!