How to write UI tests (for sublime/ and shell/)

Alexander Dymo dymo at ukrpost.ua
Fri May 2 21:55:02 UTC 2008


As I've promised before, I sat down today and wrote a small howto on UI  
testing. This basically summarizes my experience writing tests for sublime/ 
and shell. Hope you'll find this useful. If something is missing, please ping 
me ;)

Same howto is on our wiki, if you have changes or fixes, feel free to modify: 
http://www.kdevelop.org/mediawiki/index.php/How_To_Write_UI_Tests

==============================================

== How to compile and run tests ==
Just configure kdevplatform with 
  cmake -DKDE4_BUILD_TESTS=on
and you're all set.

To run all tests use
  make test
command from toplevel dir or from sublime/ and shell/ subdirs.

To run individual test just run its executable:
  cd sublime/tests
  ./sublime-viewactivationtest

To run individual test function just pass the name of the function as the 
parameter:
  cd sublime/tests
  ./sublime-viewactivationtest testActivationAfterViewRemoval

You can pass several test functions there. Also there are more options that 
tests accept (verbosity and output control, etc.). See 
http://doc.trolltech.com/4.4/qtestlib-manual.html#qtestlib-command-line-arguments 
for the reference or just run test with --help from the command line.




== UI Testing Examples ==

=== Simple Shell test from scratch ===

==== shellexampletest.h ====
<pre>
#include <QObject>
#include <tests/common/autotestshell.h>
using namespace KDevelop;

class ShellExampleTest: public QObject {
    Q_OBJECT
private slots:
    void init();
    void cleanup();
    void testSomething();
};
</pre>

==== shellexampletest.cpp ====
<pre>
#include "shellexampletest.h"
#include <tests/common/kdevtest.h>
#include <QtTest/QtTest>

void ShellExampleTest::init() {
    AutoTestShell::init();  // create instance of test shell extension
    KDevelop::Core::initialize(); // initialize kdevplatform
}
void ShellDocumentOperationTest::cleanup() {
}
void ShellDocumentOperationTest::testSomething() {
    // your test here
    // expect full blown kdevplatform-based application 
    // loaded and initialized by this moment (albeit without any plugins 
because we currently don't have them for test shell)
}
//THIS IS VERY IMPORTANT: use KDEVTEST_MAIN macro for ALL UI tests
KDEVTEST_MAIN(ShellExampleTest)
#include "shellexampletest.moc"
</pre>

==== CMakeLists.txt ====
<pre>
set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} )
include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/interfaces 
${CMAKE_SOURCE_DIR}/shell)

set( shellexampletest_SRCS shellexampletest.cpp )
kde4_add_unit_test(shell-exampletest ${shellexampletest_SRCS})
target_link_libraries(shell-exampletest ${QT_QTTEST_LIBRARY} 
${KDE4_KDEUI_LIBS} kdevplatformshell )
</pre>

=== Simple Sublime test from scratch ===
I won't post an example here because Sublime tests are just usual tests (but 
also created with KDEVTEST_MAIN macro). Some Sublime tests launch simple 
application, some of them just create several objects and test their 
functionality. Take a look at existing Sublime tests because 99% of the time 
you'll just add new test functions to them. 




== UI Testing Techniques ==
Writing UI tests is actually very easy and fun to do. Here are some useful UI 
test writing techniques.

=== Test mostly user experience instead internals ===
The main idea behind UI tests we write for Sublime and Shell is to replicate 
user actions and verify results that user would get. This way of thinking 
greatly simplifies tests, reduces the time you spend writing the test and 
actually assures that UI is never broken for the user.

Some guidelines:
* call high-level public functions that are directly called by the user 
interface and check their results, for example
    IDocumentController *documentController = 
Core::self()->documentController();
    documentController->openDocumentFromText("");
    QCOMPARE(documentController->openDocuments().count(), 1);

* find QAction objects in the UI and trigger them manually, for example:
    QAction *splitAction = 
Core::self()->uiControllerInternal()->activeMainWindow()->actionCollection()->action("split_vertical");
    QVERIFY(splitAction);
    splitAction->trigger();
    QCOMPARE(doc->views().count(), 2);

* simulate events, for example:
    qApp->sendEvent(myView->widget(), new QFocusEvent(QEvent::FocusIn));
    QCOMPARE(mainWindow->activeView(), myView);

* try to check signals you emit, especially if the signal is the part of 
public API (lots of plugins will connect to the signals and will expect them 
to work reliably), for example:
    QSignalSpy spy(controller, SIGNAL(viewAdded(Sublime::View*)));
    area->addView(doc->createView());
    QCOMPARE(spy.count(), 1);



===  Aggregate test results before making comparisons and verifications ===
With multiple QVERIFY and QCOMPARE test functions may become messy.
It is sometimes a good idea to print some data into string and compare that 
string to the original data also encoded in the string.

For an example, see sublime/tests/areaoperationtest.cpp. We print the layout 
of mainwindow's central area to the string and compare the string with 
expected layout:
 checkAreaViewsDisplay(mainWindow, area, QString("\n\
 [ vertical splitter ]\n\
     [ vertical splitter ]\n\
         [ view2.1.1 view2.1.2 ]\n\
         [ view2.4.1 ]\n\
     [ horizontal splitter ]\n\
         [ view2.5.1 ]\n\
         [ view2.3.1 ]\n\
 "));
This check assures that after we show the particular area in some mainwindow 
we get several splitters created and those splitters contain some visible 
views inside.

One such check replaces 12-15 QCOMPARE()'s and QVERIFY()'s and makes test look 
easier to understand because it's immediately obvious for the reader that 
mainwindow should have vertical splitters with two splitters inside. Both of 
internal splitters should contain some views, etc, etc.



=== Write less tests and more test functions ===
Each test class is linked to the separate executable. Linking too many 
executables may be really slow. Also each Sublime test is a full-featured KDE 
application and takes some time to start. Each Shell test is not only a KDE 
application but a KDevPlatform application so it starts even slower. 

So the morale is, do not spawn too many test executables without the real 
need. It is always possible to run just several test functions from the test 
in case you don't want to wait for the whole test to pass:
 #cd sublime/tests
 #./sublime-controllertest --functions
 testDocumentDeletion()
 testAreaDeletion()
 testNamedAreas()
 #./sublime-controllertest testNamedAreas testAreaDeletion
 ...



=== Keep test code clean ===
As I've said before, the goal of writing UI tests is to specify certain 
behavior we expect to work. Of course that specification should be succinct 
and easily understandable for the reader. Please expect that your test will 
be read by other developers. When they break your code, they will start 
looking from the test, not from the code.



=== Feel good ;) ===
Once you write the test for your feature or bugfix, you can feel safe and 
warm ;) Your work will never be broken/lost/etc. because there's now an 
automated way to assure that.




More information about the KDevelop-devel mailing list