如何在解决方案中运行所有测试

时间:2014-09-20 19:42:42

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

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

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

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

5 个答案:

答案 0 :(得分:14)

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

答案 1 :(得分: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测试运行器,这些不能合并到一个报告中。

答案 2 :(得分:2)

我不知道这是否会对您有所帮助,但我会使用Invoke-MsBuild。它是一个PowerShell模块,应该完全符合您的需求。我不知道您是否在寻找PowerShell解决方案,但效果很好!

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

答案 3 :(得分: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()来调用单个对象中的所有测试。这在调试时特别有用。
  4. 如果您正在使用像我这样的appdomains,您应该为从文件夹动态加载的DLL创建一个appdomain,并在其上使用TestAll。此外,如果您使用临时文件夹,您可能希望在测试之间清空它。这样,多个测试框架版本和多个测试将不会相互交互。特别是如果您的测试使用状态(例如静态变量),这可能是一个很好的做法。 CreateInstanceAndUnwrap在线有大量示例可以帮助您解决这个问题。

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

    HTH

答案 4 :(得分:0)

在您自己的链接中阅读警告部分。 VST 2012不再像您那样支持它。

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