【问题标题】:Running integration tests against the server in the same solution在同一解决方案中针对服务器运行集成测试
【发布时间】:2016-01-28 16:47:05
【问题描述】:

我使用 NUnit/MSTest 编写集成测试,只是因为它更容易。测试需要与同一解决方案中的 TCP 服务器通信,并且它希望同时调试测试和 TCP 服务器。有没有办法在调试模式下从解决方案启动项目(控制台应用程序)并同时调试测试方法?无论我尝试过什么,VS 都不允许。

【问题讨论】:

    标签: c# visual-studio integration-testing


    【解决方案1】:

    这是编写集成测试时的常见场景。集成测试依赖于另一个服务来启动和运行。为了处理它,我通常会调用该过程来启动,例如ConsoleApplication 项目在同一解决方案中。只需添加一个帮助类来调用该过程。

    internal class ProcessInvoker
    {
        /// <summary>
        /// Invokes the host process for test service
        /// </summary>
        public static void InvokeDummyService()
        {
            var path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
    
            ProcessStartInfo info = new ProcessStartInfo(Path.Combine(path, "DummyService.exe"));
    
            info.UseShellExecute = true;
            info.WorkingDirectory = path;
    
            var process = Process.Start(info);
        }
    
        /// <summary>
        /// Kills the process of service host
        /// </summary>
        public static void KillDummyService()
        {
            Process.GetProcessesByName("DummyService").ToList().ForEach(x => x.Kill());
        }
    }
    

    现在在 TestInitialize 和 TestCleanup 方法中,我将启动进程并终止相应的进程。

        /// <summary>
        /// Setup required before the tests of the fixture will run.
        /// </summary>
        [TestFixtureSetUp]
        public void Init()
        {
            ProcessInvoker.InvokeDummyService();
        }
    
        /// <summary>
        /// Tear down to perform clean when the execution is finished.
        /// </summary>
        [TestFixtureTearDown]
        public void TearDown()
        {
            ProcessInvoker.KillDummyService();
        }
    

    现在,附上此过程以进行调试的部分来了。这是一个相当棘手的问题。我从 Visual Studio 团队中找到了一个 VS Addin,可以将子进程自动附加到当前调试器,但它似乎只适用于“f5”调试。

    然后我找到了this SO post,它真的很有效。我在这里发布了完整的代码形式的答案,几乎没有定制:

    using System;
    using System.Runtime.InteropServices;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Linq;
    using System.Collections.Generic;
    using EnvDTE;
    
    namespace Common
    {
    
        [ComImport, Guid("00000016-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        public interface IOleMessageFilter
        {
            [PreserveSig]
            int HandleInComingCall(int dwCallType, IntPtr hTaskCaller, int dwTickCount, IntPtr lpInterfaceInfo);
    
            [PreserveSig]
            int RetryRejectedCall(IntPtr hTaskCallee, int dwTickCount, int dwRejectType);
    
            [PreserveSig]
            int MessagePending(IntPtr hTaskCallee, int dwTickCount, int dwPendingType);
        }
    
        public class MessageFilter : IOleMessageFilter
        {
            private const int Handled = 0, RetryAllowed = 2, Retry = 99, Cancel = -1, WaitAndDispatch = 2;
    
            int IOleMessageFilter.HandleInComingCall(int dwCallType, IntPtr hTaskCaller, int dwTickCount, IntPtr lpInterfaceInfo)
            {
                return Handled;
            }
    
            int IOleMessageFilter.RetryRejectedCall(IntPtr hTaskCallee, int dwTickCount, int dwRejectType)
            {
                return dwRejectType == RetryAllowed ? Retry : Cancel;
            }
    
            int IOleMessageFilter.MessagePending(IntPtr hTaskCallee, int dwTickCount, int dwPendingType)
            {
                return WaitAndDispatch;
            }
    
            public static void Register()
            {
                CoRegisterMessageFilter(new MessageFilter());
            }
    
            public static void Revoke()
            {
                CoRegisterMessageFilter(null);
            }
    
            private static void CoRegisterMessageFilter(IOleMessageFilter newFilter)
            {
                IOleMessageFilter oldFilter;
                CoRegisterMessageFilter(newFilter, out oldFilter);
            }
    
            [DllImport("Ole32.dll")]
            private static extern int CoRegisterMessageFilter(IOleMessageFilter newFilter, out IOleMessageFilter oldFilter);
        }
    
        public static class AttachDebugger
        {
            public static void ToProcess(int processId)
            {
                MessageFilter.Register();
                var process = GetProcess(processId);
                if (process != null)
                {
                    process.Attach();
                    Console.WriteLine("Attached to {0}", process.Name);
                }
                MessageFilter.Revoke();
            }
            private static Process GetProcess(int processID)
            {
                var dte = (DTE)Marshal.GetActiveObject("VisualStudio.DTE.12.0");
                var processes = dte.Debugger.LocalProcesses.OfType<Process>();
                return processes.SingleOrDefault(x => x.ProcessID == processID);
            }
        }
    }
    

    注意:需要从 AddReference -> Extentions 中添加 VS 自动化库EnvDTE

    现在在ProcessInvoker 类中,在进程启动语句之后添加对AttachDebugger 实用程序类的调用。

    var process = Process.Start(info);
    // Add this after invoking process.
    AttachDebugger.ToProcess(process.Id);
    

    当我启动一个调试测试时,它就像魅力一样。该进程被调用,附加到 VS 并能够调试其他进程代码。

    检查工作解决方案here。特别是解决方案中的WcfDynamicProxy.Tests。我在那里使用 Nunit 编写集成测试。

    【讨论】:

    • 这太棒了!您是否知道我是否可以以相同的方式启动 IISExpress 调试?我想我可以在控制台应用程序中启动自己的主机。
    • 您可以附加 IISExpress.exe 进程来调试托管项目。但是,如果您想使用特定项目启动 IISExpress,请关注this link。只需使用适当的命令行选项调用 IISExpress 进程并通过 AttachDebugger 类附加它。
    【解决方案2】:

    一次调试两个程序是不可能的,但是为什么你需要在调试模式下运行控制台应用程序呢?只需在不调试的情况下启动它,然后在调试模式下启动集成测试 - 如果您想在某个时候调试控制台应用程序而不是测试方法,您可以启动 Visual Studio 的第二个实例并将调试器附加到控制台应用程序进程并从那里进行调试。

    提示您也可以将调试器附加到以编程方式从代码中调用Debuger.Launch() 的程序。

    【讨论】:

      猜你喜欢
      • 2017-08-07
      • 2023-03-04
      • 1970-01-01
      • 2012-10-22
      • 2011-08-17
      • 1970-01-01
      • 2020-11-07
      • 1970-01-01
      • 2011-10-27
      相关资源
      最近更新 更多