2014-09-20 29 views
17

看來,這裏描述我可以使用MSTest的運行我的解決方案的所有測試在命令行一氣呵成,如果我使用/ testmetadata標誌:http://msdn.microsoft.com/en-us/library/ms182487.aspx如何運行所有測試溶液中

我在Visual Studio 2013中運行SQL Server數據庫單元測試,其中我根本沒有一個vsmdi文件,而且我也找不到添加一個的方法。我嘗試創建一個testsettings文件,但是當我調用MSTest時,它沒有發現任何測試(顯示「沒有運行測試」)。

有沒有辦法讓MSTest能夠在VS2013解決方案中運行我所有的測試?

回答

12

我想關閉這個未解決的問題。我的目的是一次性運行Hudson CI服務器的所有測試,因此我編寫了一個基本的控制檯應用程序來查找和調用解決方案文件夾內所有DLL文件的MSTest。此應用程序在項目以發佈模式構建後執行。

string execId = null; 
string className = null; 
string testName = null; 
string testResult = null; 
string resultLine = null; 
List<string> results = new List<string>(); 
XmlDocument resultsDoc = new XmlDocument(); 
XmlNode executionNode = null; 
XmlNode testMethodNode = null; 

// Define the test instance settings 
Process testInstance = null; 
ProcessStartInfo testInfo = new ProcessStartInfo() 
{ 
    UseShellExecute = false, 
    CreateNoWindow = true, 
}; 

// Fetch project list from the disk 
List<string> excluded = ConfigurationManager.AppSettings["ExcludedProjects"].Split(',').ToList(); 
DirectoryInfo assemblyPath = new DirectoryInfo(Assembly.GetExecutingAssembly().Location); 
DirectoryInfo[] directories = assemblyPath.Parent.Parent.Parent.Parent.GetDirectories(); 

// Create a test worklist 
List<string> worklist = directories.Where(t => !excluded.Contains(t.Name)) 
            .Select(t => String.Format(ConfigurationManager.AppSettings["MSTestCommand"], t.FullName, t.Name)) 
            .ToList(); 

// Start test execution 
Console.WriteLine("Starting Execution..."); 
Console.WriteLine(); 

Console.WriteLine("Results    Top Level Tests"); 
Console.WriteLine("-------    ---------------"); 

// Remove any existing run results 
if (File.Exists("UnitTests.trx")) 
{ 
    File.Delete("UnitTests.trx"); 
} 

// Run each project in the worklist 
foreach (string item in worklist) 
{ 
    testInfo.FileName = item; 
    testInstance = Process.Start(testInfo); 
    testInstance.WaitForExit(); 

    if (File.Exists("UnitTests.trx")) 
    { 
     resultsDoc = new XmlDocument(); 
     resultsDoc.Load("UnitTests.trx"); 

     foreach (XmlNode result in resultsDoc.GetElementsByTagName("UnitTestResult")) 
     { 
      // Get the execution ID for the test 
      execId = result.Attributes["executionId"].Value; 

      // Find the execution and test method nodes 
      executionNode = resultsDoc.GetElementsByTagName("Execution") 
             .OfType<XmlNode>() 
             .Where(n => n.Attributes["id"] != null && n.Attributes["id"].Value.Equals(execId)) 
             .First(); 

      testMethodNode = executionNode.ParentNode 
              .ChildNodes 
              .OfType<XmlNode>() 
              .Where(n => n.Name.Equals("TestMethod")) 
              .First(); 

      // Get the class name, test name and result 
      className = testMethodNode.Attributes["className"].Value.Split(',')[0]; 
      testName = result.Attributes["testName"].Value; 
      testResult = result.Attributes["outcome"].Value; 
      resultLine = String.Format("{0}    {1}.{2}", testResult, className, testName); 

      results.Add(resultLine); 
      Console.WriteLine(resultLine); 
     } 

     File.Delete("UnitTests.trx"); 
    } 
} 

// Calculate passed/failed test case count 
int passed = results.Where(r => r.StartsWith("Passed")).Count(); 
int failed = results.Where(r => r.StartsWith("Failed")).Count(); 

// Print the summary 
Console.WriteLine(); 
Console.WriteLine("Summary"); 
Console.WriteLine("-------"); 
Console.WriteLine("Test Run {0}", failed > 0 ? "Failed." : "Passed."); 
Console.WriteLine(); 

if (passed > 0) 
    Console.WriteLine("\tPassed {0,7}", passed); 

if (failed > 0) 
    Console.WriteLine("\tFailed {0,7}", failed); 

Console.WriteLine("\t--------------"); 
Console.WriteLine("\tTotal {0,8}", results.Count); 

if (failed > 0) 
    Environment.Exit(-1); 
else 
    Environment.Exit(0); 

我App.config文件:

<appSettings> 
    <add key="ExcludedProjects" value="UnitTests.Bootstrap,UnitTests.Utils" /> 
    <add key="MSTestCommand" value="&quot;c:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\MSTest.exe&quot; /testcontainer:&quot;{0}\bin\Release\{1}.dll&quot; /nologo /resultsfile:&quot;UnitTests.trx&quot;" /> 
</appSettings> 
5

MSTest的是一種的Visual Studio 2013年它仍然用於某些類型的測試的「過時」的測試框架,但許多其他測試,可以是現在執行生活在新的Agile Test Runner中。現在,SQL數據庫單元測試似乎仍屬於「某些類型」類別,需要通過MsTest.exe執行。

最簡單的方法是使用/TestContainer commandline switch併爲測試項目使用命名模式。這樣你就可以快速抓住具有特定命名模式的所有程序集,然後將它們提供給MsTest。簡單的powershell命令可以用來抓取所有符合你的模式的文件,然後將它們提供給命令行。

vsmdi仍然可以在Visual Studio 2013中工作,但編輯器已從工具中刪除,再加上沒有模板了。所以這很難使用。這是微軟不得不說的VSDMI的:

注意 測試列表在Visual Studio中2012不再完全支持:

  • 您不能創建新的測試列表。
  • 您不能在Visual Studio中運行測試列表測試。
  • 如果您從Visual Studio 2010升級並在解決方案中包含測試列表,則可以在Visual Studio中繼續對其進行編輯。
  • 您可以繼續使用mstest.exe從命令行運行測試列表,如上所述。
  • 如果您在構建定義中使用測試列表,則可以繼續使用它。

基本上他們告訴你停止使用這種技術,並使用TestCategory的組合來創建易於執行的測試組。

正如你可以測試容器添加多個參數,你都可以將其整合到一個呼叫:

/testcontainer:[file name]  Load a file that contains tests. You can 
            Specify this option more than once to 
            load multiple test files. 
            Examples: 
            /testcontainer:mytestproject.dll 
            /testcontainer:loadtest1.loadtest 

MsTest /testcontainer:assemblyone.dll /testcontainer:assemblytwo.dll /testcontainer:assembly3.dll 

要在多個組件同時運行MSTest的。並且不要使用XUnit .NET或NUnit,因爲如果不切換到新的Agile測試運行器,這些不能合併成一個報告。

+0

是的。但我需要1份報告。 – Nahum 2014-12-16 15:59:58

+0

對於那些給-1,你能評論的原因,所以我有機會改善答案? – jessehouwing 2014-12-17 12:50:35

+0

我擔心他們只是爲了賞金而戰。我之前看到過這種行爲。 – Nahum 2014-12-17 14:51:20

2

我不知道這是否會對您有所幫助,但我使用Invoke-MsBuild。它的PowerShell模塊應該完全符合你的需求。我不知道您是否在尋找PowerShell解決方案,但它效果很好!

它還有一個姐妹腳本Invoke-MsTest用於運行MsTest而不是MsBuild。

2

只是爲了保持完整性,我經常要運行測試,控制檯應用程序,僅僅是因爲我覺得它更容易調試這出於某種原因...多年來,我創建了幾個小測試助手來幫助我;我想你可以很容易地將它們用於CI解決方案。

我明白這完全不是你的問題;然而,因爲你正在尋找一個CI解決方案並提到視覺工作室,這應該很好地解決它。

爲了讓你知道,我的小框架比這個稍大一些,但缺少的東西很容易添加。基本上,我遺漏的是日誌記錄的一切,以及我測試不同應用程序域中的不同程序集的事實(因爲可能的DLL衝突和狀態)。更多關於下面的內容。

有一點需要注意的是,我沒有在下面的過程中發現任何異常。我的主要重點是在故障排除時輕鬆調試您的應用程序。我有一個獨立的(但類似的)CI實現,基本上在下面的註釋點上增加了try/catches。

這個方法只有一個方法:Visual Studio不會複製你引用的所有程序集;它只會複製你在代碼中使用的程序集。一個簡單的解決方法是引入一個方法(從不調用),它在您正在測試的DLL中使用一種類型。這樣,你的程序集將被複制,並且一切都將很好地工作。

下面的代碼:

static class TestHelpers 
{ 
    public static void TestAll(this object o) 
    { 
     foreach (MethodInfo meth in o.GetType().GetMethods(). 
      Where((a) => a.GetCustomAttributes(true). 
       Any((b) => b.GetType().Name.Contains("TestMethod")))) 
     { 
      Console.WriteLine(); 
      Console.WriteLine("--- Testing {0} ---", meth.Name); 
      Console.WriteLine(); 

      // Add exception handling here for your CI solution. 
      var del = (Action)meth.CreateDelegate(typeof(Action), o); 
      del(); 

      // NOTE: Don't use meth.Invoke(o, new object[0]); ! It'll eat your exception! 

      Console.WriteLine(); 
     } 
    } 

    public static void TestAll(this Assembly ass) 
    { 
     HashSet<AssemblyName> visited = new HashSet<AssemblyName>(); 
     Stack<Assembly> todo = new Stack<Assembly>(); 
     todo.Push(ass); 

     HandleStack(visited, todo); 

    } 

    private static void HandleStack(HashSet<AssemblyName> visited, Stack<Assembly> todo) 
    { 
     while (todo.Count > 0) 
     { 
      var assembly = todo.Pop(); 

      // Collect all assemblies that are related 
      foreach (var refass in assembly.GetReferencedAssemblies()) 
      { 
       TryAdd(refass, visited, todo); 
      } 

      foreach (var type in assembly.GetTypes(). 
       Where((a) => a.GetCustomAttributes(true). 
        Any((b) => b.GetType().Name.Contains("TestClass")))) 
      { 
       // Add exception handling here for your CI solution. 
       var obj = Activator.CreateInstance(type); 
       obj.TestAll(); 
      } 
     } 
    } 

    public static void TestAll() 
    { 
     HashSet<AssemblyName> visited = new HashSet<AssemblyName>(); 
     Stack<Assembly> todo = new Stack<Assembly>(); 

     foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) 
     { 
      TryAdd(assembly.GetName(), visited, todo); 
     } 

     HandleStack(visited, todo); 
    } 

    private static void TryAdd(AssemblyName ass, HashSet<AssemblyName> visited, Stack<Assembly> todo) 
    { 
     try 
     { 
      var reference = Assembly.Load(ass); 

      if (reference != null && 
       !reference.GlobalAssemblyCache &&   // Ignore GAC 
       reference.FullName != null && 
       !reference.FullName.StartsWith("ms") &&  // mscorlib and other microsoft stuff 
       !reference.FullName.StartsWith("vshost") && // visual studio host process 
       !reference.FullName.StartsWith("System")) // System libraries 
      { 
       if (visited.Add(reference.GetName()))  // We don't want to test assemblies twice 
       { 
        todo.Push(reference);     // Queue assembly for processing 
       } 
      } 
     } 
     catch 
     { 
      // Perhaps log something here... I currently don't because I don't care... 
     } 
    } 
} 

如何使用此代碼:

  1. 你可以簡單地調用TestHelpers.TestAll()測試所有組件,引用的程序集,間接引用的組件等,這可能是什麼你想在CI中做。
  2. 您可以致電TestHelpers.TestAll(assembly)測試帶有所有引用程序集的單個裝配。當你在多個程序集中拆分測試和/或在調試時,這會很有用。
  3. 您可以致電new MyObject().TestAll()調用單個對象中的所有測試。這在調試時特別有用。

如果您使用的是像我這樣的應用程序域,您應該爲從一個文件夾動態加載的DLL構建一個應用程序域,然後使用TestAll。另外,如果您使用臨時文件夾,則可能需要在測試之間清空該文件夾。這樣,多個測試框架版本和多個測試將不會相互影響。特別是如果你的測試使用狀態(例如靜態變量),這可能是一個好習慣。有很多CreateInstanceAndUnwrap在線的例子可以幫助你解決這個問題。

有一點需要注意的是,我使用代表而不是method.Invoke。這基本上意味着你的異常對象不會被Reflection所吃掉,這意味着你的調試器不會被破壞。另外請注意,我通過名稱檢查屬性,這意味着這將與不同的框架一起工作 - 只要屬性名稱匹配。

HTH