在同一解决方案中针对服务器运行集成测试

本文关键字:服务器 运行 集成测试 解决方案 | 更新日期: 2023-09-27 18:32:15

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

在同一解决方案中针对服务器运行集成测试

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

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"调试。

然后我找到了这个SO帖子,它真的很棒。我在这里发布完整的代码,几乎没有自定义:

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

注意:您需要从添加引用 -> 扩展中添加 VS 自动化库EnvDTE

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

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

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

在此处查看工作解决方案。 特别是 WcfDynamicProxy.Tests in the solution。我用Nunit在那里编写集成测试。

一次

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

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