- 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/
我对java有点陌生,所以如果我犯了一个简单的错误,请原谅我,但我不确定我哪里出错了,我收到的错误是“预期的.class,预期的标识符,而不是声明, ';'预期的。”我尝试了不同的方法,并从这些方法中
This question already has answers here: chai test array equality doesn't work as expected (3个答案) 3年前
我正在学习 Java(对不起,我的英语很差,这不是我的母语),当我在 Eclipse (JavaSE-1.7) 中在我输入的每个“try”中执行“try-finally” block 时,会出现以下消
我收到两个错误,指出 token 上的语法错误,ConstructorHeaderName expected instead & token “(”上的语法错误,< expected 在线: mTM.
我找不到错误。 Eclipse 给我这个错误。每个 { } 都是匹配的。请帮忙。 Multiple markers at this line - Syntax error on token “)”,
代码: import java.awt.*; import javax.swing.*; import java.awt.event.*; public class DoubleIt extends
我正在用 python(Vs 代码)编写代码,但出现此错误: Expected ")" Pylance 错误发生在:def main() 我试着运行我的 main 并将它打印到我的屏幕上。我用谷歌搜
我正在尝试按照 documentation 中的建议使用异步函数。但我收到此错误 意外的 token ,预期 ( async function getMoviesFromApi() { try
Closed. This question does not meet Stack Overflow guidelines。它当前不接受答案。 想改善这个问题吗?更新问题,以便将其作为on-topic
Closed. This question does not meet Stack Overflow guidelines。它当前不接受答案。 想改善这个问题吗?更新问题,以便将其作为on-topic
第一行包含一个表示数组长度的整数p。第二行包含用空格分隔的整数,这些整数描述数组中的每个元素。第三行打印一个整数,指示负数组的数量。 package asgn3; import java.util.*
好的,我是初学者,我必须修复此 java 表达式语言代码才能在我的系统 (Windchill) 中工作,但看起来我在语法中遗漏了一些内容: LWCNormalizedObject lwc =
我无法编译我的程序! 我想我缺少一个花括号,但我怎么也看不出在哪里! import javax.swing.*; import java.awt.*;
我的 jQuery 代码有问题,我的 Firebug 向我发出警告:需要选择器。 这是代码: $("img[id$='_tick']").each(function() { $(this).c
我的新类(class) Fountainofyouth 遇到了问题。尝试构建整个项目后,调试器显示 warning: extended initializer lists only available
我已经从 Java 转向 CPP,并且正在努力围绕构造构造函数链进行思考,我认为这是我的问题的根源。 我的头文件如下: public: GuidedTour(); GuidedTour(string
鉴于以下 for(var i=0; i< data.cats.length; i++) list += buildCategories(data.cats[i]); jsLint 告诉我 Expect
我有这个 json,但 Visual Studio Code 在标题中给了我警告。 [ { "title": "Book A", "imageUrl": "https:
我正在尝试编写一个有条件地禁用四个特殊成员函数(复制构造、移动构造、复制赋值和移动赋值)的包装类,下面是我用于测试目的的快速草稿: enum class special_member : uint8_
所以我用 F# 编写了一个非常简单的程序,它应该对 1000 以下的所有 3 和 5 的倍数求和: [1..999] |> List.filter (fun x -> x % 3 = 0 || x %
我是一名优秀的程序员,十分优秀!