4

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

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

我们经历了一个过程:

  • 创建连接
  • 运行测试
  • 关闭连接
  • 处置
  • 强制 GC.Collection() / GC.AwaitingPendingFinalizers()

对于每个集成测试 - 每个集成测试都在其 Fact 中配置的超时运行。

我们遇到的问题是,它出现在几次测试(比如大约 10 次左右)之后,质量中心在调用时会无限期阻塞 - 整个 Gallio 冻结并且不再响应。

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

使用 TestDriven.Net 时也会发生同样的事情 - 可以交互地运行 1 个或几个测试,但超过 10 个测试并且整个运行冻结 - 我们唯一的选择是终止 TD.Net 使用的 ProcessInvocation86.exe 进程。

有没有人有关于如何阻止这种情况发生的任何提示/技巧,或者至少将我的集成测试与这些类型的问题隔离开来——以便 QC API 无限期阻塞的测试,测试将因超时而失败并且让加里奥进入下一个测试。

更新

使用 STA 线程的提示有助于将问题向前推进一点 - 通过自定义 XUnit.Net 属性,我们现在在它自己的 STA 线程中启动测试。这已经阻止了 Gallio/TestDriven.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;
        }
    }

相反,我们现在在使用 TestDriven.Net 运行测试时会看到这样的输出 - 顺便说一下,运行同一个套件几次将导致所有测试通过,或者通常只有一两个测试失败。在第一次失败后,第二次失败会导致“卸载 appdomain 时出错”问题。

测试“IntegrationTests.Execute_Test1”失败:超过测试执行时间:30000 毫秒

测试“T:IntegrationTests.Execute_Test2”失败:卸载 appdomain 时出错。(来自 HRESULT 的异常:0x80131015) System.CannotUnloadAppDomainException:卸载 appdomain 时出错。(来自 HRESULT 的异常:0x80131015)在 System.AppDomain.Unload(AppDomain 域)在 Xunit.ExecutorWrapper.Dispose() 在 Xunit.Runner.TdNet.TdNetRunner.TestDriven.Framework.ITestRunner.RunMember(ITestListener 侦听器,程序集程序集,MemberInfo 成员) 在 TestDriven.TestRunner.ThreadTestRunner.Runner.Run() 的 TestDriven.TestRunner.AdaptorTestRunner.Run(ITestListener testListener, ITraceListener traceListener, String assemblyPath, String testPath)

4 次通过,2 次失败,0 次跳过,耗时 50.42 秒(xunit)。

我还没有确定为什么 Quality Center 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);

似乎在 ConnectProjectEx 之后调用 Connect 有可能被阻塞(但它是不确定的)。删除冗余连接调用似乎显着提高了测试的稳定性 - 正确的连接代码:

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

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

我还没有弄清楚的一件事是为什么即使使用上面包含的超时代码, Thread.Join(timeout) 也永远不会返回。您可以附加一个调试器,它只显示测试线程处于加入/等待操作中。也许与在 STA 线程中执行有关?

4

1 回答 1

1

您可以尝试在单独的线程上运行代码,然后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!");
}
于 2010-07-19T02:48:06.247 回答