четверг, 15 ноября 2012 г.

Автоматизация установки и запуска служб Windows.

При тестировании клиентской части клиент-серверных приложений мне приходилось сталкиваться с задачей автоматизации установки/удаления и запуска/остановки служб Windows, которые реализовывали серверную часть. Обычно при разворачивании и настройке приложения перед запуском всех тестов происходит установка служб, после запуска всех тестов - удаление служб (потому что мы должны привести систему в такое состояние, в котором она была до всех действий по запуску автоматизированных тестов). Также иногда в процессе выполнения тестов мне требовалось останавливать и запускать службы.

Для установки/удаления Windows-служб можно использовать следующий хелпер:


private static void ManageInstallationService(string arguments, int timeout)
        {
            const string installUtilPath = @"C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe";
            Process p = null;
            try
            {
                p = Process.Start(installUtilPath, arguments);
                p.WaitForExit(timeout);
                if (!p.HasExited)
                {
                    throw new TestExecutionException(String.Format(CultureInfo.InvariantCulture,
                        "Процесс установки/удаления сервиса {0} не завершился за установленный таймаут {1} мс.", arguments, timeout));
                }
            }
            finally
            {
                if (p != null && !p.HasExited)
                {
                    p.Kill();
                }
            }
        }

где arguments - абсолютный путь к устанавливаемой службе.


Для запуска/остановки Windows-служб можно использовать следующие методы:


 public static void StartService(string serviceName, int timeout)
        {
            var service = new ServiceController(serviceName);
            if (service.Status != ServiceControllerStatus.Running)
            {
                try
                {
                    var timeSpan = TimeSpan.FromMilliseconds(timeout);
                    service.Refresh();
                    service.Start();
                    service.WaitForStatus(ServiceControllerStatus.Running, timeSpan);
                }
                catch (System.ComponentModel.Win32Exception ex)
                {
                    throw new TestExecutionException(String.Format(CultureInfo.InvariantCulture,
                        "Не получилось запустить сервис «{0}».\n{1}\nService status {2}", service.DisplayName, ex.Message, service.Status));
                }
                catch (System.ServiceProcess.TimeoutException ex)
                {
                    throw new TestExecutionException(String.Format(CultureInfo.InvariantCulture,
                        "Не получилось запустить сервис «{0}».\n{1}\nService status {2}", service.DisplayName, ex.Message, service.Status));
                }
            }
        }
 
        
        public static void StopService(string serviceName, int timeout)
        {
            var service = new ServiceController(serviceName);
            if (service.Status != ServiceControllerStatus.Stopped)
            {
                try
                {
                    var timeSpan = TimeSpan.FromMilliseconds(timeout);
                    service.Refresh();
                    service.Stop();
                    service.WaitForStatus(ServiceControllerStatus.Stopped, timeSpan);
                }
                catch (System.ComponentModel.Win32Exception ex)
                {
                    throw new TestExecutionException(String.Format(CultureInfo.InvariantCulture,
                        "Не получилось остановить сервис «{0}».\n{1}\nService status {2}", service.DisplayName, ex.Message, service.Status));
                }
                catch (System.ServiceProcess.TimeoutException ex)
                {
                    throw new TestExecutionException(String.Format(CultureInfo.InvariantCulture,
                        "Не получилось остановить сервис «{0}В».\n{1}\nService status {2}", service.DisplayName, ex.Message, service.Status));
                }
            }
        }

пятница, 2 ноября 2012 г.

Проверки. Часть 3. Типы проверок.

Хочу рассказать свою точку зрения на то, какие бывают проверки и как можно выводить результаты наших проверок при выполнении автотеста. Этот вопрос уже обсуждался не один раз, и не в одном месте (например, очень хорошее обсуждение тут). Расскажу решение, которое выбрала я и применяю его в практике.

Я делю все проверки на 2 части:
1. Проверки, проверяющие промежуточные состояния.
2. Проверки, проверяющие тестируемую функциональность.

Проверки первого типа нам нужны для того, чтобы просто определить, что тест идет по нужному сценарию. Проверки второго типа проверяют то, ради чего пишется сам тест, какой-то кусочек функциональности. В автотесте лучше реализовать их по-разному. Почему? Чтобы при падении теста сразу было видно, в чем причина - для первого случая это может быть ошибка в коде теста, ошибка в приложении, не позволяющая дойти до шага проверки тестируемого функционала и т.д., для второго случая - баг в тестируемом функционале. Теперь о том, как я это реализую.

Проверки, проверяющие тестируемую функциональность.

 Для проверок второго типа все просто, мы используем стандартный класс любого Unit-testing фреймворка Assert или класс Verify. В таком случае результат будет выглядеть примерно так:

Assert.AreEqual failed. Expected:<6>. Actual:<-10>. После ввода некорректных данных «-10» в поле количества товара и перемещения фокуса валидация прошла некорректно.

Смотря на результат падения такого теста первый раз (т.е. когда тест до этого был зеленый-презеленый, а потом вдруг стал красный), я понимаю, что ошибка в тестируемой функциональности. Я проверяю этот тест руками (на всякий случай), подтверждаю и завожу в баг-трекинг системе баг. Теперь в следующие разы, когда я буду запускать набор регрессионых тестов (раз в день или чаще), и этот баг не будет пофикшен, я, смотря на этот результат, буду понимать, что это известный баг и не буду больше останавливать на нем свое внимание.

Проверки, проверяющие промежуточные состояния.

Если в тесте что-то пойдет не так, например, в приложении не будет найден нужный UI-контрол, то без каких-либо проверок промежуточных состояний тест в любом случае упадет с каким-то исключением, которое будет кидать либо UI-testing фреймворк, либо код нашего теста.  Например, тест может упасть с такой ошибкой:


Test method Testing.UITests.Tests.SomeTestMethod() threw exception: 
System.ArgumentOutOfRangeException: Индекс за пределами диапазона. Индекс должен быть положительным числом, а его размер не должен превышать размер коллекции.
Имя параметра: index

или с такой


Метод проверки Testing.UITests.OrganizationList.ViewOrganizationsPropertiesTestFixture.ПросмотрИнформацииОПредприятии выдал исключение: Microsoft.VisualStudio.TestTools.UITest.Extension.UITestControlNotFoundException: Дополнительные сведения При воспроизведении не удалось найти элемент управления с указанными свойствами поиска.: 
TechnologyName:  "UIA"
ControlType:  "Edit"
AutomationId:  "24763E55-DB6E-4A65-9545-5301D251FC72"

Но эти сообщения об ошибках не очень человеко-читаемы. Для вывода более понятных сообщений можно использовать проверки промежуточного состояния. Но для этого лучше использовать не Asssert, а бросать какое-либо исключение с понятным сообщением. Что нам это даст - при просмотре результатов запуска тестов в случае падения нам сразу будет понятно, что ошибка произошла не при проверке тестируемой функциональности. Понятное сообщение об ошибке может сразу подсказать нам, в чем именно заключается ошибка в тесте (например, поменялся AutomationId у контрола, либо немного поменялся сам ход сценария, например, раньше мы принимали за данное, что фокус уже стоит на каком-то элементе, а новое поведение - фокус на элемент надо ставить вручную). Можно использовать либо стандартные исключения, либо создать какое-то свое пользовательское и кидать его. Например,  я могу создать исключение TestExecutionException и кидать его в случае fail'а теста из-за невыполнения промежуточной проверки.


Пример (C#). Нам на определенном шаге теста нужно проверить, что таблица наименований товара не пустая.

if (Goods.Count == 0)
{
  throw new TestExecutionException("В таблице товаров нет ни одного товара.");
}

Тогда текст сообщения об ошибке будет следующим:

Test method Testing.UITests.Tests.SomeTestMethod() threw exception:
Testing.Common.TestExecutionException: В таблице товаров нет ни одного товара

Если лень читать Msdn, вот как создать пользовательское исключение:

    [Serializable]
    public class TestExecutionException : Exception
    {
        public TestExecutionException()
        {
        }
 
        public TestExecutionException(string message)
            : base(message)
        {
        }
 
        public TestExecutionException(string message, Exception innerException)
            : base(message, innerException)
        {
        }
 
        protected TestExecutionException(SerializationInfo info, StreamingContext context) : base(info, context)
        {
        }
    }