单元(集成)测试

时间:2016-10-09 11:48:10

标签: c# .net winforms nunit unmanaged

我有一个非托管DLL,我试图创建一个.NET包装器库,但是当我尝试对它进行NUnit(v3)测试时,我会遇到不同的行为如果它只是从一个按钮运行单击WinForm应用程序。

背景:在DLL启动期间,我调用它的Connect()方法,最终导致DLL建立TCP连接。当建立TCP连接时,我会通过将处理程序连接到其连接的"已连接"事件。 连接后,我再调用DLL上的其他命令。

在一个简单的Winforms应用程序测试中,我有一个实例化" DLL"然后调用Connect()方法。当线程完成时,应用程序闲置大约2秒钟,然后连接"事件处理程序按预期触发。该事件不会返回任何内容。

但是因为connect()是一项昂贵的操作,并且因为我的图书馆注定要使用更大的应用程序,所以我在我的库中创建了一个ConnectAsync()方法,并使用了async and await个关键字和AutoResetEventConnectAsync()方法返回"实例化"的实例。在获得通知TCP连接从事件中获得通知后的DLL。 对测试WinForms应用程序进行了一些重构,它按预期工作。

下一步是使用NUnit进行集成测试。但是,当从异步测试调用ConnectAsync()方法时,我可以在远程应用程序上看到TCP连接建立,但事件处理程序永远不会触发。一天的测试,搜索和反复试验都无法解决为什么ConnectAsync()完全可以通过一个简单的Winforms按钮而不是来自UnitTest。

这是测试代码

[Test()]
public async Task Test1()
{
    var conn = await GetConnection();
    //assert some commands on the conn
}

private async Task<TCPConnector> GetConnection()
{   
    return await Task.Run(() =>
    {
        var mre = new AutoResetEvent(false);        
        var ctrl = new TCPConnector();
        ctrl.serverName = server;
        ctrl.serverPort = serverPort;
        ctrl.onConnected += () => { mre.Set(); };
        ctrl.Connect();
        mre.WaitOne();
        return ctrl;
    });
}

我知道这不是一个严格的问题,但我很难过并且正在寻找想法。或指示按钮单击事件和NUnit测试执行之间的不同之处。

如果它对某人意味着什么,我打电话的dll是一个非托管的ActveX

UPDATE1: 如果使用MSTest它的工作原理!所以它与NUnit的创业环境有关。

UPDATE2: 通过this SO帖子中的调查,我偶然地复制了相同的行为,没有任何单元测试框架,而是通过reg免费COM。所以我现在认为它与COM如何被激活和消耗有关?

解决 终于找到了答案。 感谢Chris对this问题的回答。我所要做的就是在概述中添加一个<comInterfaceExternalProxyStub />部分和bingo

更新4

忽略上次更新和解决方案。它们包含误导和误报,以及我在COM,Regfree COM,Interop和COM Events的整个世界中工作时缺乏对我的理解。问题仍未解决。

关键问题仍然是当COM在单元测试的上下文中运行时,COM事件不会触发。当在普通的.exe中运行时,它们可以正常工作

2 个答案:

答案 0 :(得分:0)

我的猜测是,在不知道非托管DLL究竟在做什么的情况下,它是一个单线程单元(STA)COM dll。在这个线程模型中,COM interop会将对DLL的所有调用编组到创建对象的线程中(在单元测试中阻塞等待自动重置事件,因此没有任何反应)。

事件模式在Winforms应用程序中有效,因为主UI线程是STA线程(检查主方法上的属性)并且它正在传递消息,因此允许回调并且锁定被COM消息泵送取代。

如果是这种情况,测试包装器的唯一方法是创建一个STA线程,在其上运行消息泵,然后将消息传递给线程以触发创建COM对象和连接(换句话说,它是一个巨大的痛苦)。更糟糕的是,对象在客户端应用程序中也会以这种方式运行,因此除非您在包装器中创建一个STA线程并对所有调用进行编组,否则您将无法异步使用它。

答案 1 :(得分:0)

正如Chris所提到的,这是因为特定于在STA线程中使用COM Interop对象。发生这种情况是因为只能从该线程访问STA线程中创建的互操作对象(也称为事件调用)。 您需要做的就是将任何COM Interop的创建包装在单独的线程中。 像这样的东西:

private async Task<TCPConnector> GetConnection()
{   
    return await Task.Run(() =>
        {
            var mre = new AutoResetEvent(false);        
            Create(mre);
            mre.WaitOne();
            return ctrl;
        });
}

private TCPConnector ctrl;
private void Create(AutoResetEvent mre) 
{    
    
    ThreadPool.QueueUserWorkItem(o =>
    {
        ctrl = new TCPConnector();
        ctrl.serverName = server;
        ctrl.serverPort = serverPort;
        ctrl.onConnected += () => { mre.Set(); };
        ctrl.Connect();
    });
}

相关问题