Picture of Stefan Lieser
Stefan Lieser

The tricky cases in testing - GUI

This article is a revised version. It was previously published at refactoring-legacy-code.net published.

This is a series of articles. You can find the other articles here:

Unit tests of the GUI? The automated testing of the GUI (graphical user interface) of a desktop application is a greater challenge at first glance.However, a few simple tricks will get you a long way. I will show you a few simple options using the following example.

The illustration shows a simple UI that displays a list of Addresses is displayed. In the lower area there is a CheckBoxwhich the adjacent Button influenced. The example is constructed to explain some techniques. I have used it with WPF created. The techniques can be easily applied to WinForms transferred.

GUI-Tests -Clean Code Developer Akademie - Stefan Lieser

MVVM - Model View ViewModel

 

There is a View together with ViewModel and Data Binding is used. As the values in the table cannot be changed, I have refrained from using the ViewModel Address the interface INotifyPropertyChanged to implement.

In the following listings you can see the Xaml Code, the ViewModel Address and the Code Behind File.

namespace ui
{
    public class Address
    {
        public string Street { get; set; }

        public string ZIP { get; set; }

        public string City { get; set; }
    }
}
using System;
using System.Collections.Generic;
using System.Windows;

namespace ui
{
    public partial class AddressListForm : Window
    {
        public AddressListForm() {
            InitializeComponent();
            button.Click += ButtonClickHandler;
            checkMe.Click += CheckBoxClickHandler;
        }

        public void SetAdresslist(IEnumerable
addresslist) { grid.ItemsSource = addresslist; } public event Action ButtonClickEvent; internal void ButtonClickHandler(object sender, RoutedEventArgs e) { ButtonClickEvent(); } internal void CheckBoxClickHandler(object sender, RoutedEventArgs e) { if (checkMe.IsChecked.Value) { button.IsEnabled = false; } else { button.IsEnabled = true; } } } }

Display the dialog with data

 

A first semi-automated unit test ensures that the dialog is filled with data and then displayed. In a real project, such tests save you having to start the application and navigate to the appropriate place. In complex applications, this saves a lot of time. There is no need to create sample data in the databases, etc. Such test cases can be solved much better programmatically. In particular, a dialog can be tested before it is integrated into the application.

using System.ComponentModel;
using System.Threading;
using NUnit.Framework;

namespace ui.tests
{
    [TestFixture]
    public class AddressListTests
    {
        private AddressListForm sut;
        private BindingList
addresslist; [SetUp] public void Setup() { sut = new AddressListForm(); addresslist = new BindingList
{ new Adresse {PLZ = "12345", Ort = "Örtchen", Strasse = "Am Hang 5"}, new address {postcode = "54321", city = "Pusemuckel", street = "Weg 42"} }; } [Test, Apartment(ApartmentState.STA), Explicit] public void Multiple_addresses_display() { sut.SetAdresslist(addresslist); sut.ShowDialog(); } } }

In addition to the obligatory unit test, the Test attributes, the attribute Explicitso that it does not start when all tests are executed. As the test starts the dialog with ShowDialog dialog opens, it remains on the screen until you close it again. This test is only semi-automated: test data is automatically prepared and transferred to the dialog. The dialog is then opened and remains on the screen for inspection. For this to work technically, the attribute Apartment(ApartmentState.STA) must be added, otherwise WPF will complain.

Test whether a button triggers an event

 

The following unit test checks whether the expected event is triggered when the button is pressed. This test is also semi-automatic. In the test, the Arrangement phase, a lambda expression is bound to the event, which MessageBox is displayed. This way, I can start the test, press the button and then observe whether the MessageBox is displayed. Such tests become more interesting if parameters are supplied with the event. These can then be included in the message of the MessageBox to check whether the expected values are being delivered.

[Test, Apartment(ApartmentState.STA), Explicit]
public void Button_Handler_releases_Event_from_MessageBox() {
    sut.ButtonClickEvent += () => MessageBox.Show("Hello from the ButtonClickEvent");
    sut.ShowDialog();
}

Such semi-automated tests are a temporary solution in legacy code projects. Instead of starting the application with F5 and testing it completely manually, some processes can be automated in this way. Things become really smooth when the test is fully automated, i.e. when a Assert to check an assumption.

Automated test

 

[Test, Apartment(ApartmentState.STA)]
public void Button_Handler_triggers_event_off() {
    var count = 0;
    sut.ButtonClickEvent += () => count++;

    sut.ButtonClickHandler(this, new RoutedEventArgs());

    Assert.That(count, Is.EqualTo(1));
}

This test now runs completely automatically. A lambda expression is bound to the event of the button, which increments a counter. Now the button is to be pressed to check whether the counter is at 1. If I were now to start the dialog with ShowDialog the test execution would be interrupted until the dialog is closed. Consequently, the dialog would have to be opened on a separate thread. Furthermore, the question of how the button can be pressed programmatically would remain unanswered. I use a much simpler solution: I call the method in the test that is displayed in the UI on the Click event hangs. The following listing shows the relevant excerpt from the Code Behind file:

button.Click += ButtonClickHandler;

...

internal void ButtonClickHandler(object sender, RoutedEventArgs e) {
    ButtonClickEvent();
}

To use the method ButtonClickHandler in the test, I have set them to internal set. You can find details on the topic of visibility in my previous Blog post about visibility in tests.

Using the same scheme, I can now automatically check whether the state of the button is modified when the checkbox is ticked.

[Test, Apartment(ApartmentState.STA)]
public void CheckBox_switches_Button() {
    Assert.That(sut.button.IsEnabled, Is.True);

    sut.checkMe.IsChecked = true;
    sut.CheckBoxClickHandler(this, new RoutedEventArgs());
    Assert.That(sut.button.IsEnabled, Is.False);

    sut.checkMe.IsChecked = false;
    sut.CheckBoxClickHandler(this, new RoutedEventArgs());
    Assert.That(sut.button.IsEnabled, Is.True);
}

Here I first check whether the button is initially activated. I then simulate clicking on the checkbox in the test by setting the state to IsChecked and then set the handler CheckBoxClickHandler call, which will take place on Click event of the CheckBox hangs. I can then check whether the button has been modified as expected.

Conclusion

 

Of course, such logic does not belong in the view. The "right" thing to do would be to use a ViewModel that contains this logic. The ViewModel can then simply be tested automatically and influences the view by means of data binding. In the case of legacy code, however, not everything has been done "correctly", so that the techniques shown can then help to automatically test the logic in the GUI using unit tests.

Our seminars

course
Clean Code Developer Basics

Principles and tests - The seminar is aimed at software developers who are just starting to deal with the topic of software quality. The most important principles and practices of the Clean Code Developer Initiative are taught.

to the seminar "
course
Clean Code Developer Trainer

Conducting seminars as a trainer - This seminar is aimed at software developers who would like to pass on their knowledge of Clean Code Developer principles and practices or Flow Design to others as a trainer.

to the seminar "
course
Clean Code Developer CoWorking

Online CoWorking incl. coaching -
We are often asked what developers can do to keep up with the topic of clean code development. Our answer: Meet up with other Clean Code Developers online on a regular weekly basis.

to the seminar "

Leave a Comment

Your email address will not be published. Required fields are marked *

en_USEnglish