【问题标题】:How to run all tests in solution如何在解决方案中运行所有测试
【发布时间】:2014-09-20 19:42:42
【问题描述】:

如果我使用此处所述的 /testmetadata 标志,我似乎可以使用 MSTest 从命令行一次性运行解决方案中的所有测试:http://msdn.microsoft.com/en-us/library/ms182487.aspx

我在 Visual Studio 2013 中运行 SQL Server DB 单元测试,其中我似乎根本没有 vsmdi 文件,而且我也找不到添加一个文件的方法。我尝试创建一个 testsettings 文件,但在我调用 MSTest 时它没有发现任何测试(显示“没有要运行的测试”)。

有没有办法让 MSTest 在 VS2013 解决方案中运行我的所有测试?

【问题讨论】:

    标签: c# unit-testing visual-studio-2013 mstest


    【解决方案1】:

    我想结束这个悬而未决的问题。我的目的是从 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>
    

    【讨论】:

      【解决方案2】:

      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,因为如果不切换到新的敏捷测试运行器,就无法将它们组合成一份报告。

      【讨论】:

      • 是的。但我需要一份报告。
      • 对于那些给出-1的人,你能评论一下原因,以便我有机会改进答案吗?
      • 我担心他们只是为了赏金而战。我以前见过这种行为。
      • 如果您有任何想法,请添加。我不是为了赏金。我有足够的尊重:)。如果/testcontainer 选项对您不起作用,请解释它为什么不起作用。当然有限制。在最坏的情况下,您需要将不同的输出 xml 合并为一个。
      • @red888 敏捷测试运行器实际上是最新版本的 Visual Studio 附带的 vstest.console.exe。请参阅:msdn.microsoft.com/en-us/library/jj155796.aspx。在 TFS XAML 构建编辑器中,这由“敏捷测试运行程序”标识。如果您愿意,可以直接从 jenkins 调用 vstest.console.exe。
      【解决方案3】:

      我不知道这是否对你有帮助,但我使用Invoke-MsBuild。它是一个 PowerShell 模块,应该完全满足您的需求。我不知道您是否正在寻找 PowerShell 解决方案,但它非常有效!

      它还有一个姊妹脚本 Invoke-MsTest 用于运行 MsTest 而不是 MsBuild。

      【讨论】:

        【解决方案4】:

        为了完整起见,我经常想将测试作为控制台应用程序运行,只是因为我发现出于某种原因调试它要容易得多……多年来,我创建了一些小的测试助手来帮助我;我想你可以很容易地将它们与你的 CI 解决方案一起使用。

        我知道这完全不是您的问题;但是,由于您正在寻找 CI 解决方案并提到了 Visual Studio,这应该可以很好地解决它。

        只是让你知道,我的小框架比这个大一点,但是缺少的东西很容易添加。基本上,我遗漏了用于日志记录的所有内容以及我在不同应用程序域中测试不同程序集的事实(因为可能存在 DLL 冲突和状态)。更多内容如下。

        需要注意的一点是,我在下面的过程中没有发现任何异常。我的主要重点是在故障排除时使调试应用程序变得容易。我有一个单独的(但类似的)CI 实现,它基本上在下面的评论点上添加了 try/catch。

        此方法只有一个问题: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() 来调用单个对象中的所有测试。这在调试时特别有用。

        如果您像我一样使用 appdomains,您应该为从文件夹动态加载的 DLL 创建单个 appdomain,并在其上使用 TestAll。此外,如果您使用临时文件夹,您可能希望在测试之间清空该文件夹。这样,多个测试框架版本和多个测试将不会相互交互。特别是如果您的测试使用状态(例如静态变量),这可能是一个好习惯。 CreateInstanceAndUnwrap 网上有大量的例子可以帮助你解决这个问题。

        需要注意的一点是,我使用委托而不是 method.Invoke。这基本上意味着您的异常对象不会被反射吃掉,这意味着您的调试器不会被破坏。另请注意,我按名称检查属性,这意味着这将适用于不同的框架 - 只要属性名称匹配。

        HTH

        【讨论】:

          【解决方案5】:

          阅读您自己链接中的警告部分。 VST 2012 不再以您的方式支持它。

          也许这个更新版本可以帮助: http://msdn.microsoft.com/en-us/library/ms182490.aspx

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2011-04-27
            • 2016-09-16
            • 1970-01-01
            • 1970-01-01
            • 2010-09-24
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多