Как изолировать плохой COM-компонент (HP Quality Center 10.0) от приложения .Net при выполнении интеграционных тестов - PullRequest
4 голосов
/ 19 июля 2010

В настоящее время я работаю над некоторым программным обеспечением на основе .Net (.Net Framework 3.5 SP1), которое интегрируется с HP Quality Center 10.0 через его API-интерфейс COM-клиента (часто называемый TDApiOle80 или TDApiOle80.TDConnection).

Мы используем XUnit 1.6.1.1521 и Gallio 3.1.397.0 (вызываются из файла msbuild)

Мы выполняем процесс:

  • Создание соединения
  • Выполнение теста
  • Закрытие соединения
  • Удаление
  • Форсирование GC.Collection () / GC.AwaitingPendingFinalizers ()

Для каждой интеграцииtest - и каждый интеграционный тест выполняется с тайм-аутом, настроенным в его Fact.

Проблема, с которой мы столкнулись, заключается в том, что он появляется после нескольких тестов (скажем, около 10 или около того), когда Quality Center блокируется бесконечно при вызове - ивесь Gallio зависает и больше не отвечает.

Изначально мы обнаружили, что xunit.net применяет только тайм-аут к коду внутри факта - так что он будет ждать неопределенно долгоr конструктор или избавьтесь от методов для завершения - поэтому мы переместили эту логику в тело тестов только для подтверждения ... но это не решило проблему (все еще будет зависать после выполнения определенного количества тестов).

То же самое происходит при использовании TestDriven.Net - можно запустить 1 или несколько тестов в интерактивном режиме, но более 10 тестов и весь цикл останавливается - и наш единственный выбор - завершить процесс ProcessInvocation86.exe, используемый TD.Net.

Есть ли у кого-нибудь какие-либо советы или подсказки о том, как остановить это все вместе, или, по крайней мере, изолировать мои интеграционные тесты от подобных проблем - чтобы тесты, в которых API QC блокировался на неопределенный срок,тест завершится неудачно с тайм-аутом и позволит Gallio перейти к следующему тесту.

Обновление

Подсказка к использованию потока 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 - случайный запуск одного и того же пакета несколько раз приведет либо к тому, что все тесты пройдут, либо, как правило, только один или два теста не пройдены.А после первого сбоя второй сбой приводит к проблеме «Ошибка при выгрузке домена приложения».

Тест «IntegrationTests.Execute_Test1» не выполнен: превышено время выполнения теста: 30000 мс

ТестОшибка «T: IntegrationTests.Execute_Test2»: ошибка при выгрузке домена приложения.(Исключение из HRESULT: 0x80131015) System.CannotUnloadAppDomainException: ошибка при выгрузке домена приложения.(Исключение из HRESULT: 0x80131015) в System.AppDomain.Unload (домен AppDomain) в Xunit.ExecutorWrapper.Dispose () в Xunit.Runner.TdNet.TdNetRunner.TestDriven.Framework.ITestRunner.RunMember, слушатель-сборщик, член сборочной единицы ITestListener) в TestDriven.TestRunner.AdaptorTestRunner.Run (ITestListener testListener, ITraceListener traceListener, String assemblyPath, String testPath) в TestDriven.TestRunner.ThreadTestRunner.Runner.Run ()

4 пройдено, прошло 2, получено 2, пройдено 2, принято 250,42 секунды (xunit).

Мне еще предстоит выяснить, почему API Quality Center зависает на неопределенное время в произвольном порядке - это будет рассмотрено в ближайшее время.

Обновление 27/ 07/2010

Я наконец установил причину зависания - вот проблемный код:

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 Ответ

1 голос
/ 19 июля 2010

Вы можете попробовать запустить свой код в отдельном потоке, затем вызвать 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!");
}
...