[英]Run unit tests in parallel for code using Splat
我們使用 ReactiveUI 開發移動應用程序,因此使用 Splat 作為依賴注入框架。 在運行我們的單元測試時,我們決定讓它們並行運行以提高 IDE 中的反饋速度。 我們注意到某些測試失敗了,因為我們的 SUT 使用了 Splat,因此我們使用 splat 在測試中注入模擬。 我確信其他使用 Splat 的團隊也發生過這種情況,所以我想知道是否有內置的方法來繞過這個障礙。
public interface IDependency
{
void Invoke();
}
public class SUT1
{
private IDependency dependency;
public SUT1()
{
dependency = Locator.CurrentMutable.GetService<IDependency>();
}
public void TestThis()
{
dependency.Invoke();
}
}
public class SUT2
{
private IDependency dependency;
public SUT2()
{
dependency = Locator.CurrentMutable.GetService<IDependency>();
}
public void TestThisToo()
{
dependency.Invoke();
}
}
public class SUT1_Test()
{
[Fact]
public void TestSUT1()
{
var dependencyMock = new Mock<IDependency>();
Locator.CurrentMutable.Register(() => dependencyMock.Object, typeof(IDependency));
var sut = new SUT1();
sut.TestThis();
dependencyMock.Verify(x => x.Invoke(), Times.Once);
}
}
public class SUT2_Test()
{
[Fact]
public void TestSUT2()
{
var dependencyMock = new Mock<IDependency>();
Locator.CurrentMutable.Register(() => dependencyMock.Object, typeof(IDependency));
var sut = new SUT2();
sut.TestThisToo();
dependencyMock.Verify(x => x.Invoke(), Times.Once);
}
}
如果測試以在調用函數之前注入新模擬的方式運行,調用驗證將變得一團糟。 禁用並行化使我們每次都能獲得正確的結果,但代價是用於按順序運行測試的時間。
免責聲明:我不知道 SPLAT 是什么。 我寫的所有內容都是關於 C#、DI、多線程、測試和常見陷阱的通用知識。 SPLAT 有可能以某種絕妙的方式解決它。 非零機會。 接近於零。
我猜你在服務定位器(反)模式中使用 DI。 我猜你的Locator.CurrentMutable.xxx
是一個全局靜態的方便的東西,你可以在任何地方訪問它並向它請求任何東西。 所以,它被搞砸了多線程*)和/或測試。 時期。
引用自https://github.com/reactiveui/splat#service-location :
Locator.Current 是一個靜態變量,可以在啟動時設置,以使 Splat 適應其他 DI/IoC 框架。 這通常是個壞主意。
所以,嗯,是的,它是靜態的。 不好。
當您並行運行多個測試時,它們都會嘗試為同一服務注冊自己的模擬,並且它們必然會相交。 如果你的 DI 東西是合理的,它會拋出異常。 似乎不是,所以最后一次注冊可能獲勝,所以測試 A 得到了測試 B 注冊的模擬。這搞砸了驗證,因為測試 A 從模擬 B 讀取並且模擬記錄的操作來自測試 B 而不是 A,所以從 A 驗證失敗。
這是正確的行為。
錯誤出在您的測試設置、測試運行或 DI 架構中。
如果你堅持使用服務定位器模式,至少讓它成為非靜態的。 每個測試都必須有自己的解析上下文。 如果它們是共享的,測試將相交並失敗。
最簡單的解決方案是刪除服務定位器模式。 顯然這是不可能的,因為你告訴我們你有一個很大的代碼庫。
另一種選擇是使定位器去靜電。 嘗試使它可以是“上下文相關的”,以便每個測試都有自己獨立的服務提供者/定位器實例。 這將解決問題,因為注冊將發生在不同的實例上,並且不會交叉和覆蓋。
如果您的測試是普通的且內部是單線程的,則可以通過使Locator.CurrentMutable
或Locator
線程靜態或類似的東西(例如“異步上下文”或您認為更好的任何東西)來實現。 但是你應該只在測試中這樣做,因為在實際應用程序中讓它成為線程靜態可能會破壞它,因為你的應用程序在設計時沒有考慮到這一點。
最后,您可以嘗試調整測試的運行方式,而不是擺弄代碼、測試代碼或服務提供商生命周期。 如果Locator.CurrentMutable
是全局靜態的並且必須保持這樣,那么......
...那么您的測試不能在同一過程中並行運行(因為它們會相交)...
...但這並不妨礙您在單獨的進程中運行它們。
獲取文檔,查看源代碼,並為自己編寫一個全新的 xUnit 運行器,它將:
您可以將它們合並和出隊,而不是預先分配,以確保不會出現這樣一種情況:一個進程在他的 99 個測試中徘徊,因為一個測試花費了更長的時間,等等。由您決定。 最重要的是,如果您的服務定位器是全局的並且您在每個測試的基礎上在其中注冊模擬,則不要在單進程中並行化它們。
*) 是的,我知道互斥量/等等。 但它仍然是搞砸了,除非從消費者的角度來看它是不可變的,在這里它顯然不是不可變的。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.