2014-09-20 21 views
17

Sembra che posso correre tutti i miei test nella soluzione in un colpo solo dalla riga di comando utilizzando MSTest se uso il flag/testmetadata come descritto qui: http://msdn.microsoft.com/en-us/library/ms182487.aspxCome eseguire tutti i test in soluzione

Sono eseguendo i test dell'unità SQL Server DB in Visual Studio 2013, in cui non sembra che sia presente un file vsmdi e non riesco a trovare un modo per aggiungerne uno. Ho provato a creare un file di test, ma non viene rilevato alcun test (mostra "Nessun test da eseguire") quando richiamo MSTest.

C'è un modo per far eseguire a MSTest tutti i miei test in una soluzione VS2013?

risposta

12

Volevo chiudere questa domanda aperta. La mia intenzione era quella di eseguire tutti i test in una volta dal server Hudson CI, così ho scritto un'app di console di base per trovare e richiamare MSTest su tutti i file DLL all'interno della cartella della soluzione. Questa app viene eseguita dopo che il progetto è stato creato in modalità di rilascio.

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); 

file di mio 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 è una specie di quadro test "deprecato" di Visual Studio 2013. è ancora usato per alcuni tipi di test, ma molte delle altre prove che possono essere eseguito ora dal vivo nel nuovo Agile Test Runner. Ora i test dell'unità di database SQL sembrano essere ancora nella categoria "Alcuni tipi" e devono essere eseguiti tramite MsTest.exe.

Il modo più semplice è utilizzare /TestContainer commandline switch e utilizzare uno schema di denominazione per i progetti di test. In questo modo è possibile afferrare rapidamente tutti gli assiemi con uno specifico schema di denominazione e quindi inviarli a MsTest. È possibile utilizzare il semplice comando powershell per afferrare tutti i file che aderiscono al modello e quindi inviarli alla riga di comando.

Il vsmdi funzionerà ancora in Visual Studio 2013, ma l'editor è stato rimosso dallo strumento, in più non esiste più un modello per esso. Quindi è molto difficile da usare. Questo è ciò che Microsoft ha da dire circa la VSDMI di:

Attenzione liste di prova non sono più pienamente supportati in Visual Studio 2012:

  • Non è possibile creare nuovi elenchi di test.
  • Non è possibile eseguire test dell'elenco di test da Visual Studio.
  • Se si è effettuato l'aggiornamento da Visual Studio 2010 e si è avuto un elenco di test nella soluzione, è possibile continuare a modificarlo in Visual Studio.
  • È possibile continuare a eseguire l'elenco di test utilizzando mstest.exe dalla riga di comando, come descritto sopra.
  • Se si utilizza un elenco di test nella definizione build, è possibile continuare a utilizzarlo.

In pratica ti stai dicendo di smettere di usare questa tecnica e utilizzare una combinazione di TestCategory 's per creare facile da eseguire gruppi di test.

Come è possibile aggiungere più parametri per i contenitori di prova, è possibile tutto raggrupparli in una sola chiamata:

/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 

Per eseguire MSTest su più gruppi in una sola volta. E non utilizzare (ancora) XUnit .NET o NUnit, poiché non possono essere combinati in un unico report senza passare al nuovo runner di test Agile.

+0

Sì. Ma ho bisogno di 1 rapporto. – Nahum

+0

Per quelli che danno i voti di -1, puoi commentare il motivo per cui ho la possibilità di migliorare la risposta? – jessehouwing

+0

Ho paura che combattano solo per la taglia. Ho visto questo comportamento prima. – Nahum

2

Non so se questo ti sarà d'aiuto o meno, ma io uso Invoke-MsBuild. È un modulo PowerShell che dovrebbe fare esattamente ciò di cui hai bisogno. Non so se stavi cercando una soluzione PowerShell o no, ma funziona benissimo!

Ha anche uno script sorella, Invoke-MsTest per l'esecuzione di MsTest anziché MsBuild.

2

Solo per completezza, spesso mi vogliono eseguire i test come applicazione console, semplicemente perché lo trovo molto più facile eseguire il debug di questo, per qualche motivo ... Nel corso degli anni ho creato alcuni piccoli aiutanti di test per aiutarmi; Suppongo che tu possa usarli con la tua soluzione CI abbastanza facilmente.

Capisco che questa non è la tua domanda interamente; tuttavia, dal momento che stai cercando una soluzione CI e menzioni lo studio visivo, questo dovrebbe risolverlo abbastanza bene.

Solo per farvi sapere, il mio piccolo quadro è un po 'più grande di questo, ma le cose che mancano sono abbastanza facili da aggiungere. Fondamentalmente ciò che ho tralasciato è tutto per la registrazione e il fatto che eseguo test su diversi assembly in domini di app diversi (a causa di possibili conflitti e stati delle DLL). Maggiori informazioni su quello sotto.

Una cosa da notare è che non rilevo eccezioni nel processo sottostante. Il mio obiettivo principale è semplificare il debug dell'applicazione durante la risoluzione dei problemi. Ho un'implementazione separata (ma simile) per l'IC che fondamentalmente aggiunge try/catch ai commenti qui sotto.

C'è solo un fermo per questo metodo: Visual Studio non copierà tutti gli assiemi a cui si fa riferimento; copierà solo gli assembly che utilizzi nel codice. Una soluzione semplice per questo è di introdurre un metodo (che non viene mai chiamato) che utilizza un tipo nella DLL che stai testando. In questo modo, il tuo assembly verrà copiato e tutto funzionerà correttamente.

Ecco il codice:

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... 
     } 
    } 
} 

Come per utilizzare questo codice:

  1. Si può semplicemente chiamare TestHelpers.TestAll() per testare tutte le assemblee, assemblee a cui fa riferimento, assemblee indirettamente fa riferimento, ecc Questo è probabilmente ciò che vuoi fare in CI.
  2. È possibile chiamare TestHelpers.TestAll(assembly) per testare un singolo assieme con tutti i gruppi di riferimento. Questo può essere utile quando dividi i test su più assiemi e/o quando esegui il debug.
  3. È possibile chiamare new MyObject().TestAll() per richiamare tutti i test in un singolo oggetto. Questo è particolarmente utile durante il debug.

Se stai usando AppDomain come me, si dovrebbe fare un singolo dominio di applicazione per una DLL di caricare in modo dinamico da una cartella e utilizzare TestAll su questo. Inoltre, se si utilizza una cartella di lavoro, è consigliabile svuotare quella tra i test. In questo modo, più versioni di test framework e test multipli non interagiranno tra loro.In particolare se i test utilizzano lo stato (ad esempio variabili statiche), questa potrebbe essere una buona pratica. Ci sono un sacco di esempi per CreateInstanceAndUnwrap online che ti aiuteranno in questo.

Una cosa da notare è che io uso un delegato invece del method.Invoke. Questo significa fondamentalmente che il tuo oggetto di eccezione non verrà mangiato da Reflection, il che significa che il tuo debugger non sarà interrotto. Nota anche che controllo gli attributi per nome, il che significa che funzionerà con diversi framework, purché i nomi degli attributi corrispondano.

HTH

Problemi correlati