我有一個幫助我的單元測試方法,它聲稱按特定順序提出了一個特定的事件序列。代碼如下:測試助手預期事件序列報告重複事件
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);
});
}
但是結果是一樣的,包含{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引擎使用NUnit測試框架,.NET版本3.5(ISH,它更接近單聲道2.0,我相信),和Visual Studio 2013年
更新2 :
將代碼和測試提取到他們自己的項目(Unity3D生態系統之外)後,所有測試都按預期運行,所以我將不得不將這一個填充到Unity - > Visual工作室橋樑。
有趣的問題......我試着通過使用NUnit重現你的情況(我不確定這單元測試你實際使用的是儘管工具包),但我只拿到了FuelChanged事件的一個觸發(指數訂閱等於1),接着是FuelFull事件(訂閱索引等於0),然後是未通過測試的FuelNoLongerEmpty事件。 所以我必須在我的事件處理代碼中有不同的東西。 AFAIK,Lambda表達式按照定義進行懶惰評估,因此它們在實際調用之前不會被評估,而是編譯到與使用委託語法相同的東西。 – RvdV79
這確實是NUnit,我很困惑你對我有不同的結果,我會看看我的代碼,看看我是否在將它複製到問題中時有任何錯誤。 –
首先,我需要在公共靜態無效ExpectEventSequence的示例中更改原始代碼,因爲訂閱未知。因此我使用了你的最後一個循環。 除此之外,最好知道您的事件處理程序(或使用它的基礎)是什麼。我正在使用NUnit 2.6.4。如果可能的話,你可能會爲你的例子添加更多的代碼(而不會暴露給很多機密信息:)) – RvdV79