Написання автоматичних тестів для тестування інтерфейсу десктопних додатків

Постановка задачі

Завдання для прикладу у нас буде просте – є додаток із двома кнопками. Після натискання першої в текстовому полі з'явиться певний текст (нехай буде “Habrahabr”). Після натискання на другу туди ж виводитиметься поточний час та дата.

Відповідно, потрібно щонайменше три тести для наступних кейсів:

  1. Початковий текст у текстовому полі під час старту програми.
  2. Текст після натискання першої кнопки.
  3. Текст після натискання на другу кнопку.

Огляд існуючих рішень

1. .Net UI Automation

AutomationElement.RootElement.FindFirst(TreeScope.Children, New PropertyCondition(AutomationElement.NameProperty, "заголовок вікна нашого додатка")); * Цей source code був висвітлений з Source Code Highlighter .

Для специфічних контролів,UI Automationнадає набір додаткових обгорток, званихAutomationPatterns, наприкладExpandCollapsePattern,SelectionItemPatternі т.д., що дозволяє, відповідно, використовувати специфічний для цих контролів функціонал, наприклад, можливість розгорнути/згорнути експандер.

Переваги
Недоліки
Приклади тестів для поставленого завдання

  1. [TestMethod]
  2. public void TestStartup()
  3. var appPath = Path.Combine(Path.GetDirectoryName( Assembly .GetExecutingAssembly().Location), @"..\..\..\TestUI\bin\Debug\TestUI.exe" );
  4. var process = Process.Start(appPath);
  5. try
  6. Thread.Sleep(5000);
  7. var mainWindow = AutomationElement.FromHandle(process.MainWindowHandle);
  8. var buttonControl = mainWindow.FindFirst(TreeScope.Children, New PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Button));
  9. var textBoxControl = mainWindow.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Edit));
  10. var textBox = (ValuePattern)textBoxControl.GetCurrentPattern(ValuePattern.Pattern);
  11. Assert.AreEqual( "123123123" , textBox.Current.Value);
  12. >
  13. нарешті
  14. process.Kill();
  15. >
  16. >
* Цей вихідний код було виділено за допомогою підсвічування вихідного коду.

  1. [Тестовий метод]
  2. public void TestMethodUIAutomation()
  3. var appPath = Path.Combine(Path.GetDirectoryName( Assembly .GetExecutingAssembly().Location), @"..\..\..\TestUI\bin\Debug\TestUI.exe" );
  4. var process = Process.Start(appPath);
  5. спробувати
  6. Thread.Sleep(5000);
  7. var mainWindow = AutomationElement.FromHandle(process.MainWindowHandle);
  8. var buttonControl = mainWindow.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Button));
  9. var textBoxControl = mainWindow.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Edit));
  10. var textBox = (ValuePattern) textBoxControl.GetCurrentPattern(ValuePattern.Pattern);
  11. Assert.AreEqual( "123123123" , textBox.Current.Value);
  12. var button = (InvokePattern) buttonControl.GetCurrentPattern(InvokePattern.Pattern);
  13. button.Invoke();
  14. Assert.AreEqual( "Habrahabr" , textBox.Current.Value);
  15. >
  16. нарешті
  17. process.Kill();
  18. >
  19. >
* Цей вихідний код було виділено за допомогою підсвічування вихідного коду.

Текст після нажаття на другу кнопку реалізувати не вийде, враховуючи те, що поточне час завжди різне, а замокувати що-або ми можемо.

2. White project

Безкоштовний фреймворк з кодеплексу, заснований наUI Automation. Достоїнства та недоліки ті ж, відрізняється тільки більш зручним та розширеним api для роботи з деревом контролів.

Приклади тестів для поставленого завдання

  1. [TestMethod]
  2. public void TestStartup()
  3. var appPath = Path.Combine(Path.GetDirectoryName( Assembly .GetExecutingAssembly().Location), @"..\..\..\TestUI\bin\Debug\TestUI.exe" );
  4. var application = White.Core.Application.Launch(appPath);
  5. Assert.IsNotNull(application);
  6. var window = application.GetWindow("MainWindow");
  7. var textBox = window.Get();
  8. Assert.IsNotNull(textBox);
  9. Assert.AreEqual("123123123", textBox.Text);
  10. >
* Цей source code був highlighted with Source Code Highlighter.

  1. [TestMethod]
  2. public void TestWithWhite()
  3. var appPath = Path.Combine(Path.GetDirectoryName( Assembly .GetExecutingAssembly().Location), @"..\..\..\TestUI\bin\Debug\TestUI.exe" );
  4. var application = White.Core.Application.Launch(appPath);
  5. Assert.IsNotNull(application);
  6. var window = application.GetWindow("MainWindow");
  7. var textBox = window.Get();
  8. var button = window.Get (SearchCriteria.ByText("Click for test"));
  9. button.Click();
  10. Assert.AreEqual("Habrahabr", textBox.Text);
  11. >
* Цей source code був highlighted with Source Code Highlighter.

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" );

* Цей вихідний код було виділено за допомогою підсвічування вихідного коду.

  1. private void AssertRender( рядок expectImageName, FrameworkElement elementForTest)
  2. var image = elementForTest.Render();
  3. var expectPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, expectImageName);
  4. if (!( File .Exists(expectPath) && File .ReadAllBytes(expectPath).SequenceEqual(image)))
  5. Файл .WriteAllBytes(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "fail_" + expectImageName), image);
  6. throw new AssertFailedException( string .Format( "Елемент не дорівнює зображенню ''" , elementForTest.Get(x => x.Name), expectImageName));
  7. >
  8. >
  9. AssertRender( "button.png" , кнопка);
* Цей вихідний код було виділено за допомогою підсвічування вихідного коду.

Достоинства
  • Бесплатность.
  • Не вимагає встановлення додаткових бібліотек.
  • Позволяє мокувати частини системи.
Недостатки
  • Нет рекордера для тестів. Все пишем руками.
  • Підтримка тестів ціликом ложиться на ваші плечі.
Приклади тестів для поставленного завдання

  1. [Тестовий метод]
  2. public void TestStartup()
  3. var application = UI.Run(() => new App < MainWindow = new MainWindow() >);
  4. var mainWindow = application.Get(x => x.MainWindow);
  5. var textBox = mainWindow.FindChild((TextBox el) => el.Name == "SomeTexBox" );
  6. Assert.IsNotNull(textBox);
  7. Assert.AreEqual( "123123123" , textBox.Get(x=> x.Text));
  8. application.Invoke(x => x.Shutdown());
  9. >
* Цей вихідний код було виділено за допомогою підсвічування вихідного коду.

  1. [Тестовий метод]
  2. public void TestFirstButtonClick()
  3. var application = UI.Run(() => new App < MainWindow = new MainWindow() >);
  4. var mainWindow = application.Get(x => x.MainWindow);
  5. var textBox = mainWindow.FindChild((TextBox el) => el.Name == "SomeTexBox" );
  6. var button = mainWindow.FindChild((Button el) => el.Content.Equals( "Натисніть для перевірки" ));
  7. button.Raise(ButtonBase.ClickEvent);
  8. Assert.AreEqual( "Habrahabr" , textBox.Get(x => x.Text));
  9. application.Invoke(x => x.Shutdown());
  10. >
* Цей вихідний код було виділено за допомогою підсвічування вихідного коду.

  1. [Тестовий метод]
  2. [HostType( "Кроти")]
  3. public void TestSecondButton()
  4. var application = UI.Run(() => new App < MainWindow = new MainWindow() >);
  5. var mainWindow = application.Get(x => x.MainWindow);
  6. var dateTimeExpect = новий DateTime (2011, 12, 08, 12, 30, 25);
  7. MDateTime.NowGet = () => dateTimeExpect;
  8. var button = mainWindow.FindChild((Button el) => el.Content.Equals( "Натисніть для тесту 2" ));
  9. button.Raise(ButtonBase.ClickEvent);
  10. var textBox = mainWindow.FindChilds

().Перший();

  • Assert.AreEqual(dateTimeExpect.ToString(), textBox.Get(x => x.Text));
  • application.Invoke(x => x.Shutdown());
  • >
  • * Цей вихідний код було виділено за допомогою підсвічування вихідного коду.

    Тут я просто замокував викликDateTime.Nowза допомогоюфреймворкаMoles, огляд якого можна подивитися, наприклад, тут.

    Створення application і отримання вікна, природно, можна внести в статичний конструктор тестового класу, щоб прискорити час проходження сьюїти тестів і спростити їх код.

    Висновок

    А у нас тут можна отримати грант на тестовий період Яндекс.Хмари. Варто лише у полі «секретний пароль» запровадити «Хабр»