summaryrefslogtreecommitdiffstats
path: root/doc/src/qtuitest_plugins.qdoc
blob: d3161d36801307246d42142ead3cbf930c8e5b8b (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
/*!

\page qtuitest_plugins.html
\title Creating a QtUiTest Test Widget

\tableofcontents

\section1 Introduction

QtUiTest provides support for simulating high-level user interface interactions.
Conceptually these take the form of, for example, \i{Select 'Dog' from the widget labelled 'Animal'}, or \i{Enter 'Bob' into the widget labelled 'Name'}.

Crucially, testcases do not need to specify the exact type of each widget or how to interact with it.
Instead the logic for interacting with different types of widgets resides on the system under test, either alongside the implementation of each widget or in a plugin.
Each class of widget which QtUiTest can interact with has a corresponding wrapper class, referred to as a test widget.

This document explains when a test widget must be created and how it is implemented.

\section1 When a Test Widget is Required

QtUiTest includes support for most Qt widgets. When a new type of widget is introduced, a test widget may be required.

In these cases, a test widget will almost certainly have to be implemented:
\list
\o A custom widget implements painting and interaction by overriding functions such as \l{QWidget::}{paintEvent()} and \l{QWidget::}{keyPressEvent()}.
\o The behavior of a major user interface element has been customized (for example, a device profile is used which has a device-specific way of accepting incoming phone calls).
\endlist

In these cases, a test widget will usually not be required:
\list
\o A custom widget acts primarily as a container for standard widgets.
   In this case, QtUiTest can interact with the contained widgets as normal.
\o A custom widget subclasses a standard widget and correctly reimplements virtual functions.
   For example, a subclass of QAbstractItemView which performs custom painting with a custom model will work with no additional effort if \l{QAbstractItemView::}{visualRect()} is implemented correctly (for mouse interaction), standard item view key event handling is used and the item model accurately reports its data.
\endlist

As an example, consider a testcase for creating a contact in the addressbook application.
At one point in the testcase, we wish to set the contact's title to Doctor:

\c{select("Dr", "Title");}

This requires that the widget referred to by "Title" implements the \l{QtUiTest::}{SelectWidget} interface.
If this is not the case, the testcase fails with a failure message like the following:

\c{FAIL!  : sys_addressbook::creating_a_contact() ERROR: Title (QComboBox(0x80b2768)) is not a SelectWidget.}

This error message indicates that the "Title" widget, which is a QComboBox, does not have any corresponding test widget which implements the \l{QtUiTest::}{SelectWidget} interface, and therefore can't have items selected from it.

Note that not all errors arising from missing test widgets will be of this form.

\section1 Choosing the Right Interfaces

Test widgets must implement one or more of a standard set of interfaces belonging to the \l{QtUiTest} namespace.

Test widget interfaces map to the conceptual purpose of a widget from a user's perspective.
The available interfaces are listed below:

\table
\header \o Interface    \o Applies to   \o Examples
\row
\o \l{QtUiTest::}{Widget}
\o All 2D user interface elements.
\o QWidget
\row
\o \l{QtUiTest::}{ActivateWidget}
\o Widgets which are activated to trigger some action.
\o QAbstractButton
\row
\o \l{QtUiTest::}{CheckWidget}
\o Widgets which can be checked and unchecked.
\o QCheckBox, QRadioButton
\row
\o \l{QtUiTest::}{TextWidget}
\o Widgets which display text.
\o QLineEdit, QTextEdit, QMenu, QAbstractItemView, QLabel, many others
\row
\o \l{QtUiTest::}{InputWidget}
\o Widgets which can accept text input.
\o QLineEdit, QTextEdit
\row
\o \l{QtUiTest::}{ListWidget}
\o Widgets which display a list of items.
\o QAbstractItemView, QMenu
\row
\o \l{QtUiTest::}{SelectWidget}
\o Widgets which allow an item to be selected from a list.
\o QAbstractItemView, QMenu
\endtable

Each test widget should implement all interfaces applicable to the wrapped widget.
Test widgets can subclass other test widgets and reuse already-implemented interfaces.
For example, the test widget for QCheckBox could inherit from the test widget for QAbstractButton to avoid having to reimplementing the \l{QtUiTest::}{Widget}, \l{QtUiTest::}{TextWidget} and \l{QtUiTest::}{ActivateWidget} interfaces again.

Some test widget interfaces are strongly related and are likely to be implemented in pairs.
Almost all widgets which accept text input also display the entered text, so any test widget which implements \l{QtUiTest::}{InputWidget} will usually implement \l{QtUiTest::}{TextWidget}.
Almost all widgets which can be selected from also display the selectable items, so any test widget which implements \l{QtUiTest::}{SelectWidget} will usually implement \l{QtUiTest::}{ListWidget}.

\section1 Implementing the Test Widget

To make a new test widget visible to QtUiTest, there are two separate approaches which can be taken, each with their own advantages.

\section2 Plugin method

The plugin method involves adding the test widget code into a plugin which is then loaded by QtUiTest at runtime.
This is the most suitable method to use in most cases and the only method used for the test widgets shipped with QtUiTest.

Advantages to the plugin method compared to the multiple inheritance method include:
\list
\o The code for the test widget is cleanly separated from the wrapped widget and hence easy to omit from a release build without the need for \c{#ifdef}s or similar measures.
\o It is easier to reuse test widget code because test widgets aren't directly tied to wrapped widgets.
\o It is possible to customize the process of \l{QtUiTest::WidgetFactory::find()}{finding} and \l{QtUiTest::WidgetFactory::create()}{creating} test widgets.
\o Typical multiple inheritance difficulties are avoided, such as the test widget interface API shadowing the API of the wrapped widget.
\endlist

\section3 Test widget class

Each test widget class needs to inherit from QObject and the relevant test widget interfaces.

In practice, it is common for a test widget class hierarchy to be written which closely mirrors the wrapped widget class hierarchy.
This makes it possible to avoid rewriting the code for common interfaces such as \l{QtUiTest::Widget} many times.

It is possible to subclass the test widgets shipped with QtUiTest, although they are not guaranteed to remain source or binary compatible across releases.
The convention used in the reference plugins to generate a test widget class name is to take the wrapped widget class name, drop any leading Q, and prefix Test.
For example, the test widget wrappers for QWidget and QAbstractItemView are named TestWidget and TestAbstractItemView respectively.

Using the plugin approach, while subclassing from TestWidget to avoid reimplementing the \l{QtUiTest::Widget} interface, would result in a class declaration like the following:

\code
#include <testwidget.h>
class TestCustomComboBox : public TestWidget, public QtUiTest::ListWidget,
    public QtUiTest::SelectWidget, public QtUiTest::TextWidget
{
Q_OBJECT
Q_INTERFACES(
    QtUiTest::ListWidget
    QtUiTest::SelectWidget
    QtUiTest::TextWidget)

public:
    TestCustomComboBox(CustomComboBox* wrapped);
    virtual ~TestCustomComboBox();

    // QtUiTest::ListWidget members
    virtual QStringList list() const;
    virtual QRect visualRect(const QString&) const;

    // QtUiTest::SelectWidget members
    virtual bool canSelect(QString const&) const;
    virtual bool select(QString const&);

    // QtUiTest::TextWidget members
    virtual QString text() const;
    virtual QString selectedText() const;

private:
    CustomComboBox* m_wrapped;
};
\endcode

Implementing the test widget is as simple as retrieving the necessary information from the wrapped widget.
Test widgets can create and use other test widgets at runtime when necessary, as shown in the \c{list()} function below.

\code
TestCustomComboBox::TestCustomComboBox(CustomComboBox* wrapped)
    : m_wrapped(wrapped)
{}

QStringList TestCustomComboBox::list() const
{ return qtuitest_cast<QtUiTest::ListWidget*>(m_wrapped->view())->list(); }

QString TestCustomComboBox::text() const
{ return list().join("\n"); }

QString TestCustomComboBox::selectedText() const
{ return m_wrapped->currentText(); }
\endcode

Memory management is handled automatically; there will be a maximum of one TestCustomComboBox instance created for any CustomComboBox and it will be destroyed when the underlying CustomComboBox is destroyed.

\section3 Test widget factory

When using the plugin approach it is also necessary to implement a factory class.
This serves as the entry point to the plugin and handles the logic for creating test widgets.

The test widget factory must subclass \l{QtUiTest::WidgetFactory} and implement the \l{QtUiTest::WidgetFactory::}{create()} function to create test widgets and the \l{QtUiTest::WidgetFactory::}{keys()} function to report which widget classes can be wrapped.

\code
class TestWidgetsFactory : public QObject, public QtUiTest::WidgetFactory
{
    Q_OBJECT
    Q_INTERFACES(QtUiTest::WidgetFactory)

public:
    TestWidgetsFactory();

    virtual QObject* create(QObject*);
    virtual QStringList keys() const;
};
\endcode

The \l{QtUiTest::WidgetFactory::}{create()} function is called when a new test widget is to be created.
Our example widget factory handles CustomComboBox widgets and nothing else.

\code
QObject* TestWidgetsFactory::create(QObject* wrapped)
{
    if ((CustomComboBox* ccb = qobject_cast<CustomComboBox*>(wrapped))) {
        return new TestCustomComboBox(ccb);
    }
    return 0;
}
\endcode

The \l{QtUiTest::WidgetFactory::}{keys()} function must report which classes can be handled by this factory.
Any object passed into the \l{QtUiTest::WidgetFactory::}{create()} function is guaranteed to be one of the classes returned by \l{QtUiTest::WidgetFactory::}{keys()}.
Classes are handled from most to least specific; when creating a test widget for CustomComboBox, a factory which handles CustomComboBox has higher priority over a factory which handles QWidget.
If two or more factories handle the same class, it is undefined which factory will be asked to create the test widget.

Our example factory can only handle CustomComboBox widgets so it returns that class name only.
\code
QStringList TestWidgetsFactory::keys() const
{
    return QStringList() << "CustomComboBox";
}
\endcode

Finally, the plugin needs to be exported using the standard Qt plugin mechanism:
\code
#include <qplugin.h>
Q_EXPORT_PLUGIN2(customtestwidgets, TestWidgetsFactory)
\endcode

In the project's \c{qbuild.pro}, the \c{PLUGIN_TYPE} must be set to \c{qtuitest_widgets}.

\section2 Multiple inheritance method

The multiple inheritance approach requires the widget being wrapped to implement the test widget interfaces itself.

Advantages to the multiple inheritance method compared to the plugin method include:
\list
\o The code for the test widget is in the same file as the wrapped widget and hence is more likely to be maintained if the widget is modified.
\o The test widget has access to the internal structures of the wrapped widget.
\o There is no need to write a \l{QtUiTest::}{WidgetFactory} class to handle creation of test widgets.
\endlist

For example, implementing a custom combobox class along with all of the associated test widget interfaces would result in a class declaration like:

\code
class CustomComboBox : public QComboBox, public QtUiTest::Widget,
    public QtUiTest::ListWidget, public QtUiTest::SelectWidget,
    public QtUiTest::TextWidget
{
Q_OBJECT
Q_INTERFACES(
    QtUiTest::Widget
    QtUiTest::ListWidget
    QtUiTest::SelectWidget
    QtUiTest::TextWidget)

public:
    CustomComboBox(QWidget* parent = 0);
    ...

    // CustomComboBox members
    void addCustomItem(const QVariant& item);
    void addCustomItems(const QVariantList& items);
    ...

    // QtUiTest::Widget members
    virtual const QRect& geometry() const;
    virtual QRect rect() const;
    ...

    // QtUiTest::ListWidget members
    virtual QStringList list() const;
    virtual QRect visualRect(const QString&) const;
    ...

    // etc...
};
\endcode

Implementing the test widget is as simple as returning the necessary information.
Test widgets can create and use other test widgets at runtime when necessary, as shown in the \c{list()} function below.

\code
QStringList CustomComboBox::list() const
{ return qtuitest_cast<QtUiTest::ListWidget*>(view())->list(); }

QString CustomComboBox::text() const
{ return list().join("\n"); }

QString CustomComboBox::selectedText() const
{ return currentText(); }
\endcode

*/