代码之家  ›  专栏  ›  技术社区  ›  Bittercoder

在执行集成测试时,如何从.NET应用程序中隔离坏的COM组件(HP Quality Center 10.0)

  •  4
  • Bittercoder  · 技术社区  · 14 年前

    我目前正在开发一些基于.NET的软件(.NET Framework 3.5 SP1),它通过COM客户端API(通常称为TDapiole80或TDapiole80.TDconnection)与HP Quality Center 10.0集成。

    我们正在使用xunit 1.6.1.1521和galio 3.1.397.0(从msbuild文件调用)

    我们经历了一个过程:

    • 创建连接
    • 运行测试
    • 闭合连接
    • 处置
    • 强制gc.collection()/gc.waitingPendingFinalizers()。

    对于每个集成测试——并且每个集成测试都是在配置超时的情况下运行的。

    我们遇到的问题是,在几次测试(比如大约10次左右)后,质量中心会无限期地被调用,而整个galio都会冻结,不再响应。

    最初,我们发现xunit.net只将它的超时应用于事实中的代码-因此它将无限期地等待构造函数或释放方法完成-所以我们将该逻辑移入测试体中,只是为了确认…但这并没有解决问题(在运行一定数量的测试之后仍然会挂起)。

    同样的事情也发生在使用testsdriven.net时-可以交互运行1个或几个测试,但超过10个测试,整个运行将冻结-我们唯一的选择是终止td.net使用的processinvocation86.exe进程。

    是否有人对如何一起阻止这一切发生,或者至少将我的集成测试与这些问题隔离开来有任何提示/诀窍-以便在qc api无限期阻塞的测试中,测试将在超时时失败,并允许galio进入下一个测试。

    更新

    使用sta线程的提示有助于将问题向前推进一点——通过一个定制的xunit.net属性,我们现在在它自己的sta线程中启动测试。这阻止了galio/testsdriven.net完全锁定,因此我们可以在我们的Hudson构建服务器上运行集成测试。

        public class StaThreadFactAttribute : FactAttribute
        {
            const int DefaultTime = 30000; // 30 seconds
    
            public StaThreadFactAttribute()
            {
                Timeout = DefaultTime;
            }
    
            protected override System.Collections.Generic.IEnumerable<Xunit.Sdk.ITestCommand> EnumerateTestCommands(Xunit.Sdk.IMethodInfo method)
            {
                int timeout = Timeout;
    
                Timeout = 0;
    
                var commands = base.EnumerateTestCommands(method).ToList();
    
                Timeout = timeout;
    
                return commands.Select(command => new StaThreadTimeoutCommand(command, Timeout, method)).Cast<ITestCommand>();
            }
        }
    
        public class StaThreadTimeoutCommand : DelegatingTestCommand
        {
            readonly int _timeout;
            readonly IMethodInfo _testMethod;
    
            public StaThreadTimeoutCommand(ITestCommand innerComand, int timeout, IMethodInfo testMethod)
                : base(innerComand)
            {
                _timeout = timeout;
                _testMethod = testMethod;
            }
    
            public override MethodResult Execute(object testClass)
            {
                MethodResult result = null;
    
                ThreadStart work = delegate
                                                        {
                                                            try
                                                            {
                                                                result = InnerCommand.Execute(testClass);
                                                                var disposable = testClass as IDisposable;
                                                                if (disposable != null) disposable.Dispose();
                                                            }
                                                            catch (Exception ex)
                                                            {
                                                                result = new FailedResult(_testMethod, ex, this.DisplayName);
                                                            }
                                                        };
    
                var thread = new Thread(work);
    
                thread.SetApartmentState(ApartmentState.STA); //Set the thread to STA
    
                thread.Start();
    
                if (!thread.Join(_timeout))
                {
                    return new FailedResult(_testMethod, new Xunit.Sdk.TimeoutException((long)_timeout), base.DisplayName);
                }
    
                return result;
            }
        }
    

    相反,我们现在在用testsdriven.net运行测试时会看到这样的输出——偶然地运行同一个套件几次将导致所有测试通过,或者通常只有一到两个测试失败。在第一次失败之后,第二次失败会导致“卸载AppDomain时出错”问题。

    测试“integrationtests.execute”测试1 失败:超过测试执行时间: 300万毫秒

    试验 't:integrationtests.execute_test2' 失败:卸载时出错 AppDealm。(来自hresult的异常: 0x80131015) System.CanNotUnloadAppDomain异常: 卸载AppDomain时出错。 (来自hresult的异常:0x80131015) 在System.AppDomain.Unload(AppDomain 域) xUnit.ExecutorWrapper.Dispose()位于 xUnit.runner.tdnet.tdnet runner.testdriven.framework.itestrunner.runmember(itestListener) 侦听器,程序集, memberinfo成员)位于 TestDriven.TestRunner.adaptortStrunner.run(itestListener) 测试侦听器,itraceListener traceListener,字符串assemblyPath, 字符串testpath)位于 TestDriven.TestRunner.ThreadTestRunner.Runner.Run()。

    4通过,2失败,0跳过,采取 50.42秒(Xunit)。

    我还没有确定为什么质量中心API会无限期地随机挂起-将在不久后对此进行进一步调查。

    更新日期:2010年7月27日

    我最终确定了挂起的原因-下面是有问题的代码:

    connection = new TDConnection();
    connection.InitConnectionEx(credentials.Host);
    connection.Login(credentials.User, credentials.Password);
    connection.Connect(credentials.Domain, credentials.Project);
    connection.ConnectProjectEx(credentials.Domain, credentials.Project, credentials.User, credentials.Password);
    

    调用connect之后再调用connectprojectex似乎有可能被阻塞(但这是不确定性的)。删除冗余连接调用似乎大大提高了测试的稳定性-正确的连接代码:

    connection = new TDConnection();
    connection.InitConnectionEx(credentials.Host);
    connection.ConnectProjectEx(credentials.Domain, credentials.Project, credentials.User, credentials.Password);
    

    继承了代码库之后,我没有考虑过连接代码。

    有一件事我还没有弄清楚,为什么即使上面包含了超时代码,thread.join(timeout)也永远不会返回。您可以附加一个调试器,它只显示测试线程正在进行连接/等待操作。可能与在STA线程中执行有关?

    1 回复  |  直到 14 年前
        1
  •  1
  •   SLaks    14 年前

    您可以尝试在单独的线程上运行代码,然后调用 Join 在有超时的新线程上,如果超时,则终止它。

    例如:

    static readonly TimeSpan Timeout = TimeSpan.FromSeconds(10);
    public static void RunWithTimeout(ThreadStart method) {
        var thread = new Thread(method);
        thread.Start();
        if (!thread.Join(Timeout)) {
            thread.Abort();
            Assert.False(true, "Timeout!");
    }