Написання автоматичних тестів для тестування інтерфейсу десктопних додатків
Постановка задачі
Завдання для прикладу у нас буде просте – є додаток із двома кнопками. Після натискання першої в текстовому полі з'явиться певний текст (нехай буде “Habrahabr”). Після натискання на другу туди ж виводитиметься поточний час та дата.
Відповідно, потрібно щонайменше три тести для наступних кейсів:
- Початковий текст у текстовому полі під час старту програми.
- Текст після натискання першої кнопки.
- Текст після натискання на другу кнопку.
Огляд існуючих рішень
1. .Net UI Automation
AutomationElement.RootElement.FindFirst(TreeScope.Children, New PropertyCondition(AutomationElement.NameProperty, "заголовок вікна нашого додатка")); * Цей source code був висвітлений з Source Code Highlighter .
Для специфічних контролів,UI Automationнадає набір додаткових обгорток, званихAutomationPatterns, наприкладExpandCollapsePattern,SelectionItemPatternі т.д., що дозволяє, відповідно, використовувати специфічний для цих контролів функціонал, наприклад, можливість розгорнути/згорнути експандер.
Переваги
Недоліки
Приклади тестів для поставленого завдання
- [TestMethod]
- public void TestStartup()
- var appPath = Path.Combine(Path.GetDirectoryName( Assembly .GetExecutingAssembly().Location), @"..\..\..\TestUI\bin\Debug\TestUI.exe" );
- var process = Process.Start(appPath);
- try
- Thread.Sleep(5000);
- var mainWindow = AutomationElement.FromHandle(process.MainWindowHandle);
- var buttonControl = mainWindow.FindFirst(TreeScope.Children, New PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Button));
- var textBoxControl = mainWindow.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Edit));
- var textBox = (ValuePattern)textBoxControl.GetCurrentPattern(ValuePattern.Pattern);
- Assert.AreEqual( "123123123" , textBox.Current.Value);
- >
- нарешті
- process.Kill();
- >
- >
- [Тестовий метод]
- public void TestMethodUIAutomation()
- var appPath = Path.Combine(Path.GetDirectoryName( Assembly .GetExecutingAssembly().Location), @"..\..\..\TestUI\bin\Debug\TestUI.exe" );
- var process = Process.Start(appPath);
- спробувати
- Thread.Sleep(5000);
- var mainWindow = AutomationElement.FromHandle(process.MainWindowHandle);
- var buttonControl = mainWindow.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Button));
- var textBoxControl = mainWindow.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Edit));
- var textBox = (ValuePattern) textBoxControl.GetCurrentPattern(ValuePattern.Pattern);
- Assert.AreEqual( "123123123" , textBox.Current.Value);
- var button = (InvokePattern) buttonControl.GetCurrentPattern(InvokePattern.Pattern);
- button.Invoke();
- Assert.AreEqual( "Habrahabr" , textBox.Current.Value);
- >
- нарешті
- process.Kill();
- >
- >
Текст після нажаття на другу кнопку реалізувати не вийде, враховуючи те, що поточне час завжди різне, а замокувати що-або ми можемо.
2. White project
Безкоштовний фреймворк з кодеплексу, заснований наUI Automation. Достоїнства та недоліки ті ж, відрізняється тільки більш зручним та розширеним api для роботи з деревом контролів.
Приклади тестів для поставленого завдання
- [TestMethod]
- public void TestStartup()
- var appPath = Path.Combine(Path.GetDirectoryName( Assembly .GetExecutingAssembly().Location), @"..\..\..\TestUI\bin\Debug\TestUI.exe" );
- var application = White.Core.Application.Launch(appPath);
- Assert.IsNotNull(application);
- var window = application.GetWindow("MainWindow");
- var textBox = window.Get();
- Assert.IsNotNull(textBox);
- Assert.AreEqual("123123123", textBox.Text);
- >
- [TestMethod]
- public void TestWithWhite()
- var appPath = Path.Combine(Path.GetDirectoryName( Assembly .GetExecutingAssembly().Location), @"..\..\..\TestUI\bin\Debug\TestUI.exe" );
- var application = White.Core.Application.Launch(appPath);
- Assert.IsNotNull(application);
- var window = application.GetWindow("MainWindow");
- var textBox = window.Get();
- var button = window.Get (SearchCriteria.ByText("Click for test"));
- button.Click();
- Assert.AreEqual("Habrahabr", textBox.Text);
- >
3. Visual Studio 2010 Coded UI Test
Переваги
Недоліки
У цілому нині, набір недоліків той самий, як і вUI Automation. Окремо треба виділити те, що можливість роботи є лише у певних версіях2010 студії (Ultimate, Premium, Professional). Причому якщо запуск тестів можливий у всіх трьох, створення відповідного типу item'а в проекті і запуск рекордера можливий тільки у версіях Ultimate і Premium. І якщо для своїх домашніх проектів і можна завантажити з торентів купити Ultimate версію, то для комерційного проекту, де йдеться про ліцензію для десятків розробників, такий крок може натрапити на нерозуміння з боку вищого начальства та бухгалтерії.
Код тестів я наводити не буду, зважаючи на те, що він є автогенерованим і тому особливого інтересу не представляє.
4. Test Complete та йому подібні
Системи, подібні до можна узагальнити в одну групи. Я не описуватиму їх докладно, т.к. це тема окремої статті, виокремлю лише деякі моменти. Основна їх перевага - широкий спектр застосувань, відсутність необхідності в навичках програмування і наявність окремої системи, що не вимагають Visual Studio, зі створення, зберігання та підтримки тестів. Недоліки ж повторюють попередні рішення — платність, ставлення до системи, що тестується, як до «чорної скриньки», без можливості мокування плюс має власне середовище для запуску тестів, так що використовуватися звичайний mstest не вийде.
Зробимо щось своє
Якщо ви пишіть UI своєї програми на WPF, то для його тестування можна скористатися класом VisualTreeHelper. Алгоритм досить простий - запускаємо в тест-методі наш додаток в окремому потоці, отримуємо черезVisualTreeHelperпотрібний контрол, емулюємо евенти і зчитуємо значення для асертів.
Для зручнішого створення тестів, для себе я зробив невеликий утилітний клас, який спрощує виконання рутинних дій:
* Цей source codeбуло виділено за допомогою підсвічування вихідного коду.
var window = application.Get(x => x.MainWindow);
* Цей вихідний код було виділено за допомогою підсвічування вихідного коду.
var textBox = _mainWindow.FindChild((TextBox el) => el.Name == "SomeText" );
* Цей вихідний код було виділено за допомогою підсвічування вихідного коду.
- private void AssertRender( рядок expectImageName, FrameworkElement elementForTest)
- var image = elementForTest.Render();
- var expectPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, expectImageName);
- if (!( File .Exists(expectPath) && File .ReadAllBytes(expectPath).SequenceEqual(image)))
- Файл .WriteAllBytes(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "fail_" + expectImageName), image);
- throw new AssertFailedException( string .Format( "Елемент не дорівнює зображенню ''" , elementForTest.Get(x => x.Name), expectImageName));
- >
- >
- AssertRender( "button.png" , кнопка);
Достоинства
- Бесплатность.
- Не вимагає встановлення додаткових бібліотек.
- Позволяє мокувати частини системи.
Недостатки
- Нет рекордера для тестів. Все пишем руками.
- Підтримка тестів ціликом ложиться на ваші плечі.
Приклади тестів для поставленного завдання
- [Тестовий метод]
- public void TestStartup()
- var application = UI.Run(() => new App < MainWindow = new MainWindow() >);
- var mainWindow = application.Get(x => x.MainWindow);
- var textBox = mainWindow.FindChild((TextBox el) => el.Name == "SomeTexBox" );
- Assert.IsNotNull(textBox);
- Assert.AreEqual( "123123123" , textBox.Get(x=> x.Text));
- application.Invoke(x => x.Shutdown());
- >
- [Тестовий метод]
- public void TestFirstButtonClick()
- var application = UI.Run(() => new App < MainWindow = new MainWindow() >);
- var mainWindow = application.Get(x => x.MainWindow);
- var textBox = mainWindow.FindChild((TextBox el) => el.Name == "SomeTexBox" );
- var button = mainWindow.FindChild((Button el) => el.Content.Equals( "Натисніть для перевірки" ));
- button.Raise(ButtonBase.ClickEvent);
- Assert.AreEqual( "Habrahabr" , textBox.Get(x => x.Text));
- application.Invoke(x => x.Shutdown());
- >
- [Тестовий метод]
- [HostType( "Кроти")]
- public void TestSecondButton()
- var application = UI.Run(() => new App < MainWindow = new MainWindow() >);
- var mainWindow = application.Get(x => x.MainWindow);
- var dateTimeExpect = новий DateTime (2011, 12, 08, 12, 30, 25);
- MDateTime.NowGet = () => dateTimeExpect;
- var button = mainWindow.FindChild((Button el) => el.Content.Equals( "Натисніть для тесту 2" ));
- button.Raise(ButtonBase.ClickEvent);
- var textBox = mainWindow.FindChilds
().Перший();
Тут я просто замокував викликDateTime.Nowза допомогоюфреймворкаMoles, огляд якого можна подивитися, наприклад, тут.
Створення application і отримання вікна, природно, можна внести в статичний конструктор тестового класу, щоб прискорити час проходження сьюїти тестів і спростити їх код.
Висновок
А у нас тут можна отримати грант на тестовий період Яндекс.Хмари. Варто лише у полі «секретний пароль» запровадити «Хабр»