summaryrefslogtreecommitdiffstats
path: root/doc/src/qtuitest_tutorial.qdoc
blob: 50a7853ad1619fdd0e43c69cf2f1422dd0a4d880 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
/*!
    \page qtuitest-tutorial.html
    \contentspage QtUiTest Manual
    \nextpage {Chapter 1: Writing a System Test}{Chapter 1}

    \title QtUiTest Tutorial

    This tutorial gives a short introduction on the creation of System Tests using the QtUiTest framework. It is divided into the following chapters:

    \list 1
    \o \l {Chapter 1: Writing a System Test}{Writing a System Test}
    \o \l {Chapter 2: Input, Output and Widget Navigation}{Input, Output and Widget Navigation}
    \o \l {Chapter 3: Data Driven Testing}{Data Driven Testing}
    \o \l {Chapter 4: Putting It All Together}{Putting It All Together}
    \endlist
*/

/*!
    \page qtuitest-tutorial1.html
    \contentspage {QtUiTest Tutorial}{Contents}
    \nextpage {Chapter 2: Input, Output and Widget Navigation}{Chapter 2}

    \title Chapter 1: Writing a System Test

    In this first chapter we will demonstrate how to write a simple system test, and how to execute it.

    \section1 Setting up the environment

    Tests are saved in directories, one directory per test. As a convention we keep the name of the directory the same as the test name although this isn't a requirement. Tests are further stored in a 'tests' directory immediately under the application or library that is being tested. So let's create our first test directory, and we'll be writing a test for the addressbook application.

\code
    cd src/applications/addressbook
    mkdir -p tests/sys_demo
\endcode

    Note that we write and save tests in the \c source tree, and we can execute them from the source as well as the build tree.

    \section1 Writing the Test

    New system tests are defined by creating a new QtScript file (named ending with .js by convention), containing a \c{testcase} object with one or more test functions:
    So cd into \c tests/my_demo and create a file named \c sys_demo.js with the following content:
    \code
        testcase = {
            testFunction1: function() {
                print( "Hello World" );
            },
            testFunction2: function() {
                print( "Testfunction 2" );
            }
        }
    \endcode

    The example above shows the creation of a testcase object containing two test functions, \c testFunction1 and \c testFunction2. Obviously not much exciting is going to happen when we run this test. But we'll get to that a little later.

    The second bit that needs to be done is tell the build system about our new project. For this we need to create a project file named \c qbuild.pro. The contents of the file is as follows:
    \code
        CONFIG+=systemtest
        TARGET=sys_demo
        SOURCES+=sys_demo.js
    \endcode

    \section2 Running the test for the first time
    We can simply run the test as follows:
    \code
        cd <your_src_location>/src/applications/addressbook/tests/sys_demo
        make test
    \endcode

    The test will be built and executed and results in something like this:
    \code
1 echo '#!/bin/sh' > <your_build_dir>/src/applications/addressbook/tests/sys_demo/sys_demo
2 echo 'exec <your_build_dir>/bin/qtuitestrunner <your_src_location>/src/applications/addressbook/tests/sys_demo/sys_demo.js "$@"' >> <your_build_dir>/src/applications/addressbook/tests/sys_demo/sys_demo
3 chmod 755 <your_build_dir>/src/applications/addressbook/tests/sys_demo/sys_demo
4 export QPEHOME=$(mktemp -d /tmp/qtopia_maketest_XXXXXX); <your_build_dir>/src/applications/addressbook/tests/sys_demo/sys_demo $ARGS; rm -rf $QPEHOME
5 ********* Start testing of sys_demo *********
6 Config: Using QTest library 4.4.2, Qt 4.4.2
7 PASS   : sys_demo::initTestCase()
8 QDEBUG : sys_demo::testFunction1() Hello World
9 PASS   : sys_demo::testFunction1()
10 QDEBUG : sys_demo::testFunction2() Testfunction 2
11 PASS   : sys_demo::testFunction2()
12 PASS   : sys_demo::cleanupTestCase()
13 Totals: 4 passed, 0 failed, 0 skipped
14 ********* Finished testing of sys_demo *********
\endcode

In the above snippet line numbers have been added for clarity. Lines 1-4 are a bit of magic to create a subdirectory for the test in the build tree, and to create an 'executable' for the test. Once the executable is created, it is executed and the results are printed to stdout (or to a file if we use extra command line parameters).

Line 5-14 are the result of the test being execute.
Line 5 simply marks the start of the test.
Line 6 shows the version numbers of Qt and Qtest that are used.
Line 7 is the result of a special function named \c initTestCase. Note that we haven't written an initTestCase yet, so this function is empty (and shouldn't fail).
Once the test is initialized (with initTestCase) the testrunner will start executing all testfunctions, in principle in the order in which they are discovered in the file.
Line 8 is the result of the print statement we did in testFunction1. Note also that the output clearly marks this information as being a part of \c sys_demo::testFunction1.
Line 9 is the final test result for testFunction1. Since we did nothing else but a print statement it PASSes the test.
Line 10 and 11 show the results for testFunction2.
Line 12 shows the result for the counterpart to initTestCase: after all testfunctions have been executed the system takes a moment to clean up any garbage that has been created during test execution. Just like it's init counterpart is cleanupTestCase \c always called, and called only once, at the end of a test. Since we haven't actually defined anything yet the cleanup is empty and we'd expect it to PASS.
Line 13 shows the accumulated totals for the test and
Line 14 finally marks the end of the test.

In this example we have called \c {qbuild test} from the source tree, but we could have done the same, and with the same result from the build tree. This is because we're calling qbuild which knows everything about the actual configuration we're testing, as well as where to find it.

    \section2 Special functions

    As with \l{QTestLib Manual}{QTestLib} unit tests, special reserved functions include the four \l{QTestLib Manual#creating-a-test}{init/cleanup functions}. These behave identically to their unit test counterparts but will be explained here for completeness.

    \table 80%
    \header \o function \o description
    \row \o initTestCase \o called once immediately after the test is started. If initTestCase fails, which could happen if you add verification steps in the function, no test will be executed. In normal circumstances, each test will be executed after initTestCase is finished.
    \row \o init \o called immediately \c before a test function is executed. If a test function has multiple test data sets (to be discussed later) then \c init() will also be called multiple times. When init() fails, the test function will be skipped.
    \row \o cleanup \o called immediately after a test function has finished executing. cleanup() is guaranteed to be called, even if the test function has failed, i.e. you still get a chance to cleanup the environment after a failure.
    \row \o cleanupTestCase \o called once after the last test function has been executed.
    \endtable

    Let's re-write our testcase a bit, but this time we use the special functions.

    \code
        testcase = {
            initTestCase: function() {
                print( "Init complete test" );
            },
            cleanupTestCase: function() {
                print( "Cleanup complete test" );
            },
            init: function() {
                print( "Init test function" );
            },
            cleanup: function() {
                print( "Cleanup test function" );
            },
            testFunction1: function() {
                print( "Hello World" );
            },
            testFunction2: function() {
                print( "Testfunction 2" );
            }
        }
    \endcode

    When we run \c {qbuild test} again we get the following output:
    \code
********* Start testing of sys_demo *********
Config: Using QTest library 4.4.2, Qt 4.4.2
QDEBUG : sys_demo::initTestCase() Init complete test
PASS   : sys_demo::initTestCase()
QDEBUG : sys_demo::testFunction1() Init test function
QDEBUG : sys_demo::testFunction1() Hello World
QDEBUG : sys_demo::testFunction1() Cleanup test function
PASS   : sys_demo::testFunction1()
QDEBUG : sys_demo::testFunction2() Init test function
QDEBUG : sys_demo::testFunction2() Testfunction 2
QDEBUG : sys_demo::testFunction2() Cleanup test function
PASS   : sys_demo::testFunction2()
QDEBUG : sys_demo::cleanupTestCase() Cleanup complete test
PASS   : sys_demo::cleanupTestCase()
Totals: 4 passed, 0 failed, 0 skipped
********* Finished testing of sys_demo *********
    \endcode

    \section2 Interacting with the System Under Test

    System tests do not have direct access to the code under test. Instead, the system testrunner connects to the application, and sends and receives information via a communication protocol. This effectively controls access to the tested system, reducing the impact of tests on results and emulating standard user behaviour.

    Suppose we wish to create a simple test which launches an application; we first need to ensure that Qt Extended has finished loading successfully, since we can't be
    sure of the state of the system we have connected to. This is accomplished by using the \l{QtopiaSystemTest::}{waitForQtopiaStart()} function in
    \l{QTestLib Manual#creating-a-test}{initTestCase()}:

    \code
        initTestCase: function()
        {
            waitForQtopiaStart();
        }
    \endcode


    Since \c initTestCase() is called before any test functions are executed, this will pause test execution until it receives confirmation that Qt Extended has started. Now we're ready to start the application:

    \code
        testFunction1: function()
        {
            startApplication( "Contacts" );
        }
    \endcode

    The \l{QSystemTest::}{startApplication()} method will attempt to start the specified application (in this case, Contacts).  If the specified application cannot be started, \c{testFunction} will generate a test failure and abort the test.

    Once the application has started, we can begin testing. While it is possible to execute as many actions in a test function as desired, the recommendation is to dedicate each test function to a single use case. The test function should then attempt to realistically simulate each step of the use case, and verify that the expected result occurs. For example, assume we need to test creating a new contact; a simple test function could look like this:

    \code
        creating_a_contact: function()
        {
            // Start the application
            startApplication( "Contacts" );

            // Open the options menu and choose "New contact"
            select( "New contact", optionsMenu() );

            // Enter some details in the "Name" and "Emails" fields
            enter( "Frank Grimes", "Name" );
            enter( "frank@example.com", "Emails" );

            // Select 'Back' from the softkey menu to commit changes
            select( "Back", softMenu() );

            // We should now be at the contacts list.
            // Verify that we can select the contact we just created.
            select( "Frank Grimes" );

            // We should now be viewing the contact.
            // Move to "Details" tab.
            select( "Details", tabBar() );

            // Now verify that the details screen contains
            // the expected details.
            var text = getText();
            verify( text.contains("Frank Grimes") );
            verify( text.contains("frank@example.com") );
        }
    \endcode

    This test function will start Contacts, navigate through several screens, input data, and verify that the data was successfully saved as a new contact.
    It is important to note that this test has not explicitly simulated any key or mouse events (although this can be done).  This means that the same test can be used as-is on both a keyboard and touchscreen device.  This is possible because the implementation of select() and enter() know how to select items and enter data on both keyboard and touchscreen devices.

    The next chapter gives more detail on how navigation works.
*/

/*!
    \page qtuitest-tutorial2.html
    \previouspage {Chapter 1: Writing a System Test}{Chapter 1}
    \contentspage {QtUiTest Tutorial}{Contents}
    \nextpage {Chapter 3: Data Driven Testing}{Chapter 3}

    \title Chapter 2: Input, Output and Widget Navigation

    System tests generally consist of a series of actions and verification that the expected behaviour took place.  When testing a Qt Extended application, this usually takes the form of simulating the input of specific text and verifying that the application subsequently displays the correct text.  Testing necessarily includes navigating through forms and menus, activating buttons and comboboxes, and similar tasks.  QtUiTest makes this very easy by supplying simple yet powerful navigation and input/output methods.

    \section1 Basic Input

    Using QtUitest, it is possible to simulate individual keyclicks, perhaps the most simple form of user interaction that can be simulated.

    \code
    mytestcase = {
        mytestfunction: function() {
            keyClick(Qt.Key_A);
            keyClick(Qt.Key_B);
            keyClick(Qt.Key_C);
        }
    }
    \endcode

    In the above example, the calls to \l{QSystemTest::}{keyClick()} simulate a few individual key clicks, exactly as if the user had physically pressed keys on the device.

    However, there are several disadvantages to this approach.  Firstly, it is extremely verbose.  Secondly, it means that the test will only work on a keypad device.  To avoid these problems, the \l{QSystemTest::}{enter()} function is provided:

    \code
    mytestcase = {
        mytestfunction: function() {
            enter("abc");
        }
    }
    \endcode

    On a keypad device, the above example has the same affect as the previous example, but is more concise.  However, this test now has the additional advantage that it can work on a touchscreen device, as \l{QSystemTest::}{enter()} knows how to simulate the necessary touchscreen events to input the given text.

    \section1 Input Into Specific Widgets

    In both of the examples above, input would be delivered to whichever widget is currently focused.  In practice, what we often wish to do is enter text into a series of widgets displayed to the user.

    For example, consider the following screen for entering the details of a new contact.

    \image fields_example.png

    It would be possible to enter text in each field on this screen by using \c {enter()} and \c{keyClick(Qt.Key_Down)}.  However, the test would easily break if the order of fields were changed, or if fields were added or removed, and the test would not work on a touchscreen device.

    To solve this problem, \c {enter()} (and many other functions in QtUiTest) take an optional \i{Query Path} parameter, which specifies which widget to operate on.  The most common usage of query paths takes the form "LabelText", which refers to any widget which can receive input and is named or labeled "LabelText".

    Entering text into a few fields on the screen featured above is achieved by the following example:

    \code
    mytestfunction: function() {
        enter("Yoyo Corporation", "Company");
        enter("Yoyo Engineer", "Title");
        enter("x51 YOYO", "Phone");
    }
    \endcode

    In the above example, if any of the specified fields are moved, the test will continue to work as expected.  If any of the specified fields are removed, renamed, or no longer visible, the test will fail with a message indicating it cannot find the specified field.

    \section1 Selecting Items from Lists and Menus, and Activating Buttons

    Often, a user will be presented with a list of items or a context menu.  QtUiTest provides a simple way to navigate to and select items in any list- or menu-like widget.  The \l{QSystemTest::}{select()} function will select an item from a list, combo box, menu or tab widget by looking at the display text for each item.  It can also be used to activate a button with the given text.

    \code
    mytestfunction: function() {
        // Select "Show contacts" from the options (context) menu.
        select("Show contacts", optionsMenu());

        // Select "Bob" in the currently shown list.
        select("Bob");

        // Activate the "Edit" button to edit Bob's details.
        select("Edit");

        // Fill in the "Gender" field (a combo box) for Bob.
        select("Male", "Gender");
    }
    \endcode

    select() allows a test to be reused by keypad and touchscreen devices.  For instance, consider select("Edit") from the above example.  On a touchscreen device, this will simply result in a click being simulated at the co-ordinates of the Edit button.  On a keypad device, keyclicks will be simulated to navigate to and activate the Edit button.

    \section1 Output

    Built-in Qt and Qt Extended widgets can be queried for their currently displayed text.  The \l{QSystemTest::}{getText()} function is the most common way of doing this.

    \code
    mytestfunction: function() {
        enter("Yoyo Corporation", "Company");
        compare( getText("Company"), "Yoyo Corporation" );
    }
    \endcode

    The above example simply enters "Yoyo Corporation" into the "Company" field, then verifies that the field actually contains the text "Yoyo Corporation". Note that it is not necessary to explicitly call compare immediately after the enter command: it would have failed if the enter text had failed.

    The next chapter explains how to make use of data driven testing.
*/

/*!
    \page qtuitest-tutorial3.html
    \previouspage {Chapter 2: Input, Output and Widget Navigation}{Chapter 2}
    \contentspage {QtUiTest Tutorial}{Contents}
    \nextpage {Chapter 4: Putting It All Together}{Chapter 4}

    \title Chapter 3: Data driven testing

    The first page of this tutorial gave an example of testing creation of a contact:

    \code
        creating_a_contact: function()
        {
            // Start the application
            startApplication( "Contacts" );

            // Open the options menu and choose "New contact"
            select( "New contact", optionsMenu() );

            // Enter some details in the "Name" and "Emails" fields
            enter( "Frank Grimes", "Name" );
            enter( "frank@example.com", "Emails" );

            // Select 'Back' from the softkey menu to commit changes
            select( "Back", softMenu() );

            // We should now be at the contacts list.
            // Verify that we can select the contact we just created.
            select( "Frank Grimes" );

            // We should now be viewing the contact.
            // Move to "Details" tab.
            select( "Details", tabBar() );

            // Now verify that the details screen contains
            // the expected details.
            var text = getText();
            verify( text.contains("Frank Grimes") );
            verify( text.contains("frank@example.com") );
        }
    \endcode

    One problem with this test function is that only one set of values is tested.
    If we want to test additional values, the best way to do it is with QtUiTest's
    built-in support for data driven testing.

    We create a new object, named the same as our testfunction with '_data' appended,
    and we make the object consist of a series of arrays:

    \code
        creating_a_contact_data: {
            simple:          [ "Frank Grimes",        "frank@example.com", "+61 0321 FRANK" ],
            no_email:        [ "Bob Jones",           undefined,          "+61 0321 BOB"   ],
            with_middlename: [ "Jane Middlename Doe", "jmd@example.com",      undefined        ]
        }
    \endcode

    \c{testcase.creating_a_contact_data} is a table of test data with rows \c{simple}, \c{no_email} and \c{with_middlename}.  Each row has 3 columns, and these are passed to the \c{creating_a_contact} test function as the arguments \c{name}, \c{email}, \c{homephone} in the example below:

    \code
        creating_a_contact: function(name, email, homephone)
        {
            // Start the application
            startApplication( "Contacts" );

            // Open the options menu and choose "New contact"
            select( "New contact", optionsMenu() );

            // Enter details
            enter( name, "Name" );
            enter( email, "Emails" );
            enter( homephone, "Home Phone" );

            // Select 'Back' from the softkey menu to commit changes
            select( "Back", softMenu() );

            // We should now be at the contacts list.
            // Verify that we can select the contact we just created.
            select( name );

            // We should now be viewing the contact.
            // Move to "Details" tab.
            select( "Details", tabBar() );

            // Now verify that the details screen contains
            // the expected details.
            var text = getText();
            if (name != undefined)      verify( text.contains(name) );
            if (email != undefined)     verify( text.contains(email) );
            if (homephone != undefined) verify( text.contains(homephone) );
        }
    \endcode

    This test is now much more extensible. New test cases can simply be added as new rows to the testdata table and will automatically be tested without needing to add further logic to the test function.
*/

/*!
    \page qtuitest-tutorial4.html
    \previouspage {Chapter 3: Data Driven Testing}{Chapter 3}
    \contentspage {QtUiTest Tutorial}{Contents}

    \title Chapter 4: Putting It All Together

    Using the simple building blocks of textual input and output and navigation functions, with the data driven testing approach, a full testcase can be written for creating a new contact.  Note that the functions for creating and verifying a contact have been moved out into helper functions; this allows them to be reused for subsequent tests.  This is very useful for cases where, for instance, one test's precondition might be that a contact has been successfully created.

    \code
    testcase = {
        initTestCase: function() {
            // Start "Contacts" when the testcase starts.
            startApplication( "Contacts" );
        },

        creating_a_contact: function(name, emails, company, jobTitle, businessPhone) {
            create_contact(name, emails, company, jobTitle, businessPhone);
            verify_contact(name, emails, company, jobTitle, businessPhone);
        },

        creating_a_contact_data: {
            simple:           [ "Billy Jones",      "billy@example.com",   "Hotdog Inc.",     "Hotdog Engineer",    "12345"   ],
            letters_in_phone: [ "Joan Example",     "joan@example.com",    "Example Inc.",    "Exemplary Engineer", "555 EXA" ],
            three_names:      [ "Jon John Johnson", "jjj@example.com",     "Sillynames Inc.", "Dog Walker",         "12345"   ],
            no_job:           [ "William Doe",      "bill@example.net",     undefined,         undefined,            undefined ]
        }
    }

    // Create a contact with the given details.
    function create_contact(name, emails, company, jobTitle, businessPhone) {
        // Verify that we're on the home screen of the "Contacts" app.
        waitForTitle( "Contacts" );

        // Select "New contact" from context menu.
        select( "New contact", optionsMenu() );

        // Navigate to the "Contact" tab.
        // This is the default tab, but with this code we ensure the
        // test will work if it becomes no longer the default.
        select( "Contact", tabBar() );

        // Fill in fields on the "Contact" tab.
        // enter() returns immediately if we try to enter an undefined
        // value.
        enter( name,   "Name" );
        enter( emails, "Emails" );

        // Navigate to the "Business" tab.
        select( "Business", tabBar() );

        // Fill in fields on the "Business" tab.
        enter( company,       "Company" );
        enter( jobTitle,      "Title" );
        enter( businessPhone, "Phone" );

        // Press the Back key to commit the new contact
        select( "Back", softMenu() );
    }

    // Verify that a contact with the given details exists.
    function verify_contact(name, emails, company, jobTitle, businessPhone) {
        // Verify that we're on the contacts list.
        waitForTitle( "Contacts" );

        // Select the contact with the given name.
        select( name );

        // Navigate to the "Details" tab and get the displayed text.
        select( "Details", tabBar() );
        var details = getText();

        // Now verify that the details contains all of the non-null information
        // for the contact.
        if (name != undefined)          verify( details.contains(name) );
        if (emails != undefined)        verify( details.contains(emails) );
        if (company != undefined)       verify( details.contains(company) );
        if (jobTitle != undefined)      verify( details.contains(jobTitle) );
        if (businessPhone != undefined) verify( details.contains(businessPhone) );
    }
    \endcode

    The above test has been written to be reasonably permissive; it will succeed as long as the text shown by the contacts application contains all of the information for the created contact, and it does not test things such as the formatting of the given text, and does not test every single field.  However, this test is well insulated against minor changes to the tested application GUI.

    QtUiTest allows the tester to decide how strict a test should be.  If pixel-perfect accuracy is required for output, a test can be written to test every screen with \l{QSystemTest::}{verifyImage()}.  In contrast, a high-level text-based approach as shown above can result in effective tests which remain correct even when significant UI changes occur.

    There are many other methods available for use; for further information, refer to the \l{QSystemTest} documentation.
 */