⚙️ Всем привет! Меня зовут Алексей, я Engineer с 5-летним стажем в автоматизации тестирования. Работаю с различными инструментами автоматизации, включая веб и десктопные решения на C#.
В своей предыдущей статье я рассказывал об автоматизации тестирования с Selenium и C#. Теперь хочу поделиться практикой автоматизации десктопных приложений на примере проекта UIAutomationTestKit.
Содержание
Зачем нужна автоматизация тестирования?
1. Экономия времени и ресурсов
-
Ручное тестирование требует значительных временных затрат, особенно при регрессионном тестировании
-
Автоматизация позволяет запускать сотни тестов за минуты вместо часов ручной работы
-
Параллельное выполнение тестов значительно ускоряет процесс проверки
2. Повышение качества продукта
-
Раннее обнаружение ошибок в процессе разработки
-
Стабильность проверки критических сценариев
-
Повторяемость тестовых сценариев без человеческого фактора
3. Масштабируемость тестирования
[Test] public void Test10_RegistrationSeveralUsers([Values(3)] int number) { // Тест может быть легко масштабирован для проверки // любого количества пользователей for (int i = 0; i < number; i++) { _mainWindowController .SetValidDataInUserForm() .AssertIsRegistrationButtonEnabled() .ClickRegistrationButton(); } }
4. Интеграция в процесс разработки
-
CI/CD пайплайны — автоматический запуск тестов при каждом коммите
-
Непрерывное тестирование — постоянный контроль качества
-
Быстрая обратная связь для разработчиков
5. Документирование функционала
-
Тесты как документация — каждый тест описывает ожидаемое поведение
-
Живые примеры использования функционала
-
Регрессионная защита при рефакторинге
6. Экономическая эффективность
-
Снижение затрат на ручное тестирование
-
Высвобождение ресурсов для более сложных задач
-
Окупаемость в долгосрочной перспективе
7. Улучшение процесса разработки
-
Быстрое обнаружение проблем с архитектурой
-
Уверенность при внесении изменений
💡 Автоматизация тестирования — это не просто инструмент, а стратегическое решение, которое влияет на весь процесс разработки и качество конечного продукта.
Почему я создал этот проект?
Когда я начал работать с автоматизацией десктопных приложений, я столкнулся с несколькими проблемами:
-
Мало примеров
-
Сложно найти работающие решения
-
Нет готовых шаблонов для организации кода
-
Отсутствие лучших практик
Это подтолкнуло меня к созданию проекта, который бы решал эти проблемы и помогал другим разработчикам начать работу с автоматизацией десктопных приложений. Особенно важным для меня было создать проект, который можно использовать как основу для своих решений.
Проблемы десктопной автоматизации
1. Сложность локации элементов
// Сложный путь к элементу var element = window.FindFirstDescendant(cf => cf.ByAutomationId("StartButton")) .FindFirstChild(cf => cf.ByClassName("Button")) .AsButton();
-
Разные типы элементов — необходимость работы с WinForms, WPF, UWP
-
Динамические элементы — элементы могут появляться/исчезать
-
Отсутствие уникальных идентификаторов — сложность поиска нужных элементов
2. Нестабильность UI
// Необходимость ожиданий public static bool WaitUntilClickable(this AutomationElement element, int timeoutMs = DefaultTimeout) { _logger.Info($"Ожидание кликабельности элемента: {element.Properties.AutomationId}"); return Retry.WhileFalse( () => element?.IsEnabled == true && element?.IsOffscreen == false, TimeSpan.FromMilliseconds(timeoutMs)).Success; }
-
Сложная обработка асинхронности — элементы могут появляться с задержкой
-
Проблемы с фокусом — элементы могут быть неактивны или перекрыты
-
Нестабильные идентификаторы — элементы могут менять свои свойства
3. Масштабирование и поддержка
[Test] public void Test10_RegistrationSeveralUsers([Values(3)] int number) { try { for (int i = 0; i < number; i++) { _mainWindowController .SetValidDataInUserForm() .AssertIsRegistrationButtonEnabled() .ClickRegistrationButton() .Pause(1000); } } catch (Exception exception) { _loggerHelper.LogFailedResult(_testName, exception, _reportService); throw; } }
-
Сложность параллельного запуска — тесты могут конфликтовать друг с другом
-
Трудное управление данными — необходимость синхронизации тестовых данных
-
Сложность отладки — трудно понять, что пошло не так при падении теста
4. Отчетность и логирование
private static readonly ILogger _logger = LogManager.GetCurrentClassLogger(); _logger.Info($"Ожидание появления элемента");
-
Сложная генерация отчетов — отсутствие встроенных инструментов для создания понятных отчетов
-
Необходимость детального логирования — для анализа проблем и отладки
-
Отсутствие стандартизации — каждый проект решает эти вопросы по-своему
Эти и другие проблемы я постарался решить в своем проекте UIAutomationTestKit, создав гибкую и расширяемую архитектуру.
Инструменты и решения
Почему я выбрал FlaUI?
-
Поддержка различных UI фреймворков
// Работа с разными типами элементов var wpfButton = element.AsButton(); // WPF var winFormsButton = element.AsButton(); // WinForms var uwpButton = element.AsButton(); // UWP-
Единый API для работы с WinForms, WPF и UWP
-
Не нужно писать отдельный код для каждого фреймворка
-
Упрощает поддержку гибридных приложений
-
-
Удобный API для работы с элементами
// Простой поиск элементов var button = window.FindFirstDescendant(cf => cf.ByAutomationId("loginButton")); // Цепочка методов window.FindFirstDescendant(cf => cf.ByAutomationId("username")) .AsTextBox() .Enter("test");-
Интуитивно понятный синтаксис
-
Поддержка цепочек методов
-
Богатый набор методов для работы с элементами
-
-
Встроенная поддержка ожиданий
// Ожидание появления элемента var element = window.WaitUntilClickable(cf => cf.ByAutomationId("button")); // Ожидание исчезновения элемента window.WaitUntilDisappears(cf => cf.ByAutomationId("loading"));-
Готовые методы для работы с асинхронностью
-
Удобные таймауты и повторные попытки
-
Упрощает работу с нестабильными элементами
-
-
Активное сообщество
-
Хорошая документация
-
Быстрые ответы на вопросы
-
Открытый исходный код
-
Почему я выбрал NLog?
-
Гибкая настройка логов
<!-- Пример конфигурации NLog --> <targets> <target name="file" xsi:type="File" fileName="${basedir}/logs/${shortdate}.log" layout="${longdate}|${level:uppercase=true}|${logger}|${message}" /> <target name="console" xsi:type="Console" layout="${time}|${level:uppercase=true}|${message}" /> </targets>-
Различные форматы вывода
-
Множество целей для логов
-
Гибкая настройка уровней логирования
-
-
Производительность
// Асинхронное логирование _logger.Info("Начало выполнения операции"); await Task.Run(() => { // Длительная операция }); _logger.Info("Операция завершена");-
Асинхронное логирование
-
Минимальное влияние на производительность
-
Эффективная работа с большими объемами логов
-
-
Богатый функционал
// Структурированное логирование _logger.Info("Пользователь {username} выполнил вход", username); // Логирование исключений try { // Код, который может вызвать исключение } catch (Exception ex) { _logger.Error(ex, "Ошибка при выполнении операции"); }-
Структурированное логирование
-
Поддержка различных форматов
-
Удобная работа с исключениями
-
-
Интеграция с другими инструментами
-
Поддержка различных форматов вывода
-
Интеграция с системами мониторинга
-
Возможность отправки логов в различные системы
-
💡 Выбор FlaUI и NLog позволил мне создать надежную и масштабируемую систему автоматизации, которая легко поддерживается и расширяется.
Архитектура проекта
Мой проект организован следующим образом:
UiAutoTests/ ├── Extensions/ # Расширения для работы с элементами ├── Tests/ # Тестовые сценарии ├── Services/ # Сервисы для работы с приложением ├── Locators/ # Локаторы элементов ├── Helpers/ # Вспомогательные классы ├── Controllers/ # Контроллеры для работы с окнами ├── ControllerAssertions/ # Проверки для контроллеров ├── Clients/ # Клиенты для внешних сервисов ├── Assertions/ # Проверки ├── Core/ # Ядро фреймворка ├── TestCasesData/ # Данные для тестов ├── TestDataJson/ # JSON файлы с тестовыми данными └── NLog.config # Конфигурация логирования
Page Object Pattern vs Controller Pattern
Page Object Pattern
public class LoginPage { private readonly AutomationElement _page; public LoginPage(AutomationElement page) { _page = page; } public void EnterUsername(string username) { _page.FindFirstDescendant(cf => cf.ByAutomationId("username")) .AsTextBox() .Enter(username); } public void EnterPassword(string password) { _page.FindFirstDescendant(cf => cf.ByAutomationId("password")) .AsTextBox() .Enter(password); } public void ClickLogin() { _page.FindFirstDescendant(cf => cf.ByAutomationId("loginButton")) .AsButton() .Click(); } }
Controller Pattern
public class LoginController { private readonly AutomationElement _page; public LoginController(AutomationElement page) { _page = page; } public LoginController EnterUsername(string username) { _page.FindFirstDescendant(cf => cf.ByAutomationId("username")) .AsTextBox() .Enter(username); return this; } public LoginController EnterPassword(string password) { _page.FindFirstDescendant(cf => cf.ByAutomationId("password")) .AsTextBox() .Enter(password); return this; } public LoginController ClickLogin() { _page.FindFirstDescendant(cf => cf.ByAutomationId("loginButton")) .AsButton() .Click(); return this; } }
Преимущества Controller Pattern
-
Цепочка методов
// Вместо loginPage.EnterUsername("user"); loginPage.EnterPassword("pass"); loginPage.ClickLogin(); // Можно писать loginController .EnterUsername("user") .EnterPassword("pass") .ClickLogin(); -
Улучшенная читаемость
// Тест становится более понятным [Test] public void Test_Login() { new LoginController(_window) .EnterUsername("user") .EnterPassword("pass") .ClickLogin() .AssertLoginSuccess(); } -
Упрощенное поддержание
-
Меньше дублирования кода
-
Легче добавлять новые методы
-
Проще рефакторить
-
Почему такая архитектура?
-
Разделение ответственности
-
Каждый компонент отвечает за свою часть
-
Легко находить и исправлять ошибки
-
Простое добавление нового функционала
-
-
Масштабируемость
// Легко добавлять новые контроллеры public class MainWindowController { public UserFormController OpenUserForm() { // Логика открытия формы return new UserFormController(_window); } } -
Поддержка
-
Четкая структура проекта
-
Понятная организация кода
-
Легкое обучение новых членов команды
-
-
Тестируемость
-
Изолированные компоненты
-
Простое создание моков
-
Удобное написание тестов
-
Для кого этот проект?
Начинающие
-
Готовые примеры кода с подробными комментариями
-
Структурированная архитектура
-
Лучшие практики написания качественных автотестов
Опытные разработчики
-
Новые идеи для организации кода
-
Продвинутые техники работы
-
Готовые решения для типичных задач автоматизации
Заключение
💡 В этой статье мы рассмотрели основные концепции и структуру проекта. Теперь вы можете самостоятельно изучить код, запустить тесты и попробовать разобраться, как это работает. В следующих статьях мы подробно разберем каждый ключевой компонент:
1. Работа с UI элементами
-
Как мы ищем элементы с помощью FlaUI
-
Наши подходы к ожиданию элементов
-
Работа с разными типами UI элементов
-
Обработка динамических элементов
2. Организация тестов
-
Как устроены наши тестовые сценарии
-
Работа с тестовыми данными
-
Обработка ошибок в тестах
-
Как мы пишем стабильные тесты
3. Логирование
-
Настройка и использование NLog
-
Как мы логируем действия в тестах
-
Структура наших логов
-
Анализ результатов тестов
Мы прошли путь от понимания проблем десктопной автоматизации до создания рабочего решения. Наш проект UIAutomationTestKit — это не просто набор кода, а результат борьбы с реальными проблемами, с которыми сталкивается каждый, кто начинает автоматизировать десктопные приложения.
Советы тем, кто начинает
-
Не бойтесь рефакторить — даже если страшно.
-
Смотрите чужие проекты — идеи повсюду.
-
Тестируйте на разных версиях Windows — сюрпризы гарантированы 😅
-
Делайте скриншоты при падении — потом будете смеяться над ошибками.
💡 Мы будем развивать проект и дополнять статьи новыми примерами и лучшими практиками. Подписывайтесь. Мы тут надолго 😉
Полезные ресурсы
ссылка на оригинал статьи https://habr.com/ru/articles/914636/
Добавить комментарий