2016-11-16 115 views
1

我有一個使用xUnit.net的大型測試集(5k +),並且在並行運行的測試中遇到併發問題。如何在xUnit中記錄測試的執行順序

xUnit隨機化測試的執行順序,這使我很難檢測到問題。

我想知道在測試執行期間是否有方法記錄測試開始和結束的時刻。

注意:使用構造函數和disposer方法不會削減它,因爲您無法知道哪個測試正在構造函數/處理器上運行。注2:如果不明顯,我正在尋找不涉及在每個測試中寫日誌調用的解決方案。

感謝,

+0

而不是記錄 - 讓你的測試彼此獨立。你使用什麼樣的測試跑步者?如果沒有記錯,Visual Studio將設置爲「並行測試」,將只運行不同線程上不同項目的測試。同一個項目的測試將在同一個線程上執行。 – Fabio

+0

@Fabio,我不知道我的測試在哪裏共享數據。我懷疑這是NSubstitute。我正在努力記錄,以便能夠使他們獨立。 xUnit並行運行測試。 – LMB

+0

來自xUnit文檔:_相同測試類中的測試不會並行運行。所以你需要確定測試類沒有共享狀態。 – Fabio

回答

2

好吧,我設法利用的xUnit的BeforeAfterTestAttribute做到這一點。然後,我在下面寫了實用程序記錄器,將結果輸出到.csv文件。

public class LogTestExecutionAttribute: BeforeAfterTestAttribute 
{ 
    public override void Before(MethodInfo methodUnderTest) 
    { 
     TestExecutionDataLogger.LogBegin(methodUnderTest); 
    } 

    public override void After(MethodInfo methodUnderTest) 
    { 
     TestExecutionDataLogger.LogEnd(methodUnderTest); 
    } 
} 

public static class TestExecutionDataLogger 
{ 
    private static readonly string LogFileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "DbCoud", $"UnitTests_{DateTime.UtcNow:yyyy_MM_dd_HH_mm}_D_{AppDomain.CurrentDomain.Id}.csv"); 

    private static int _startedOrder = 0; 
    private static int _endedOrder = 0; 
    private static readonly ConcurrentDictionary<string, testExecutionData> testDataDict = new ConcurrentDictionary<string, testExecutionData>(); 
    private static readonly ConcurrentQueue<string> logQueue = new ConcurrentQueue<string>(); 

    public static void LogBegin(MethodInfo testInfo) 
    { 
     var name = $"{testInfo.DeclaringType.FullName}.{testInfo.Name}"; 
     var order = Interlocked.Add(ref _startedOrder, 1); 
     var startedUtc = DateTime.UtcNow; 
     var data = testDataDict.GetOrAdd(name, new testExecutionData()); 
     data.StartedUtc = startedUtc; 
     data.StartedOrder = order; 
     data.TestName = name; 
     data.Status = "Started"; 
     data.StartThreadId = Thread.CurrentThread.ManagedThreadId; 
     writeLog(data); 
    } 

    public static void LogEnd(MethodInfo testInfo) 
    { 
     var name = $"{testInfo.DeclaringType.FullName}.{testInfo.Name}"; 
     var dataEndedUtc = DateTime.UtcNow; 
     var order = Interlocked.Add(ref _endedOrder, 1); 
     var data = testDataDict[name]; 
     data.EndedUtc = dataEndedUtc; 
     data.EndedOrder = order; 
     data.Status = "Ended"; 
     data.EndThreadId = Thread.CurrentThread.ManagedThreadId; 
     writeLog(data); 
    } 

    private static void writeLog(testExecutionData data) 
    { 
     logQueue.Enqueue(data.ToCsvLine()); 

     if (data.EndedOrder == 1) 
     { 
      Directory.CreateDirectory(Path.GetDirectoryName(LogFileName)); 
      Task.Run(logWriter); 
     } 
    } 

    private static Task logWriter() 
    { 
     while (true) 
     { 
      var logs = new List<string>(); 
      string result; 
      while (logQueue.TryDequeue(out result)) 
      { 
       logs.Add(result); 
      } 
      if (logs.Any()) 
      { 
       File.AppendAllLines(LogFileName, logs); 
      } 
     } 
    } 

    private class testExecutionData 
    { 
     public int StartedOrder { get; set; } 
     public int EndedOrder { get; set; } 
     public DateTime StartedUtc { get; set; } 
     public DateTime EndedUtc { get; set; } 
     public string TestName { get; set; } 
     public string Status { get; set; } 
     public int StartThreadId { get; set; } 
     public int EndThreadId { get; set; } 

     public string ToCsvLine() { return $"{TestName};{Status};{StartedOrder};{EndedOrder};{StartedUtc:o};{EndedUtc:o};{Math.Max(0, (EndedUtc - StartedUtc).TotalMilliseconds)};{StartThreadId};{EndThreadId}"; } 
    } 
} 

若要使用此代碼,添加到LogTestExecutionAttribute要記錄的測試類(或基類; P)。