Viadeo Twitter Google Bookmarks ! Facebook Digg del.icio.us MySpace Yahoo MyWeb Blinklist Netvouz Reddit Simpy StumbleUpon Bookmarks Windows Live Favorites 
Logo Documentation Qt ·  Page d'accueil  ·  Toutes les classes  ·  Toutes les fonctions  ·  Vues d'ensemble  · 

Creating a QtUiTest Test Widget

Introduction

QtUiTest provides support for simulating high-level user interface interactions. Conceptually these take the form of, for example, Select 'Dog' from the widget labelled 'Animal', or 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.

When a Test Widget is Required

QtUiTest includes support for most widgets used throughout Qt Extended. 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:

  • A custom widget implements painting and interaction by overriding functions such as paintEvent() and keyPressEvent().
  • 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).

In these cases, a test widget will usually not be required:

  • A custom widget acts primarily as a container for standard widgets. In this case, QtUiTest can interact with the contained widgets as normal.
  • 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 visualRect() is implemented correctly (for mouse interaction), standard item view key event handling is used and the item model accurately reports its data.

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:

select("Dr", "Title");

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

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 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.

Choosing the Right Interfaces

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

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

InterfaceApplies toExamples
WidgetAll 2D user interface elements.QWidget
ActivateWidgetWidgets which are activated to trigger some action.QAbstractButton
CheckWidgetWidgets which can be checked and unchecked.QCheckBox, QRadioButton
TextWidgetWidgets which display text.QLineEdit, QTextEdit, QMenu, QAbstractItemView, QLabel, many others
InputWidgetWidgets which can accept text input.QLineEdit, QTextEdit
ListWidgetWidgets which display a list of items.QAbstractItemView, QMenu
SelectWidgetWidgets which allow an item to be selected from a list.QAbstractItemView, QMenu

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 Widget, TextWidget and 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 InputWidget will usually implement TextWidget. Almost all widgets which can be selected from also display the selectable items, so any test widget which implements SelectWidget will usually implement ListWidget.

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.

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:

  • 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 #ifdefs or similar measures.
  • It is easier to reuse test widget code because test widgets aren't directly tied to wrapped widgets.
  • It is possible to customize the process of finding and creating test widgets.
  • Typical multiple inheritance difficulties are avoided, such as the test widget interface API shadowing the API of the wrapped widget.

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 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 QtUiTest::Widget interface, would result in a class declaration like the following:

    #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;
    };

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 list() function below.

    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(); }

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.

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 QtUiTest::WidgetFactory and implement the create() function to create test widgets and the keys() function to report which widget classes can be wrapped.

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

    public:
        TestWidgetsFactory();

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

The create() function is called when a new test widget is to be created. Our example widget factory handles CustomComboBox widgets and nothing else.

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

The keys() function must report which classes can be handled by this factory. Any object passed into the create() function is guaranteed to be one of the classes returned by 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.

    QStringList TestWidgetsFactory::keys() const
    {
        return QStringList() << "CustomComboBox";
    }

Finally, the plugin needs to be exported using the standard Qt plugin mechanism:

    #include <qplugin.h>
    Q_EXPORT_PLUGIN2(customtestwidgets, TestWidgetsFactory)

In the project's qbuild.pro, the PLUGIN_TYPE must be set to qtuitest_widgets.

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:

  • 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.
  • The test widget has access to the internal structures of the wrapped widget.
  • There is no need to write a WidgetFactory class to handle creation of test widgets.

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

    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...
    };

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 list() function below.

    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(); }

Customizing Behavior for Standard UI Elements

In test cases it is possible to refer to abstract UI elements such as the home screen or the soft menu bar with code such as the following:

    select("New contact...", optionsMenu());

In the above example, the optionsMenu() function automatically resolves to whatever widget is responsible for showing the options menu for the standard Qt Extended user interface. Similar functions include tabBar(), progressBar(), softMenu() and launcherMenu().

If the Qt Extended UI is heavily customized, it may be necessary to override the behavior of these functions. WidgetType enumerates the types of widgets which can have their resolution overridden this way.

Overriding the behavior for a particular WidgetType requires a WidgetFactory to be implemented (as detailed above) and the find() function to be overridden. This function may return an actual widget or a test widget. Returning a test widget is useful in cases where a particular WidgetType does not map to exactly one actual widget.

For example, consider a device with two significant customizations. The options menu, typically a simple QMenu raised by a context key on the device, has been moved to an always-onscreen singleton widget. The device can also accept and hangup calls by sliding the top half of the device up or down (which is seen by Qt as a special key click), as well as through the typical dedicated keys and items in the options menu.

In this example, for the options menu it is sufficient for find() to simply return a pointer to the new options menu widget. For the call manager, find() must construct and return a special test widget which knows how to simulate all the necessary key clicks for call management as well as interacting with the options menu.

The following find() function achieves this:

    QObject* CustomFactory::find(QtUiTest::WidgetType type)
    {
        // For the options menu, use our permanently onscreen widget
        if (type == QtUiTest::OptionsMenu)
            return OnScreenOptionsMenu::instance();

        // For the call manager, create our test widget which knows how to simulate
        // device-specific key clicks.  Since there is no underlying widget,
        // just create and reuse a single test widget throughout the whole lifetime
        // of the program.
        if (type == QtUiTest::CallManager) {
            static TestCustomCallManager testCallManager;
            return &testCallManager;
        }

        // For all other widget types, just use the default find behavior.
        return 0;
    }

Publicité

Best Of

Actualités les plus lues

Semaine
Mois
Année

Le Qt Quarterly au hasard

Logo

Des apparences personnalisées utilisant les feuilles de style

Qt Quarterly est la revue trimestrielle proposée par Nokia et à destination des développeurs Qt. Ces articles d'une grande qualité technique sont rédigés par des experts Qt. Lire l'article.

Communauté

Ressources

Liens utiles

Contact

  • Vous souhaitez rejoindre la rédaction ou proposer un tutoriel, une traduction, une question... ? Postez dans le forum Contribuez ou contactez-nous par MP ou par email (voir en bas de page).

Qt dans le magazine

Cette page est une traduction d'une page de la documentation de Qt, écrite par Nokia Corporation and/or its subsidiary(-ies). Les éventuels problèmes résultant d'une mauvaise traduction ne sont pas imputables à Nokia. Qt qtextended4.4
Copyright © 2012 Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon, vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts. Cette page est déposée à la SACD.
Vous avez déniché une erreur ? Un bug ? Une redirection cassée ? Ou tout autre problème, quel qu'il soit ? Ou bien vous désirez participer à ce projet de traduction ? N'hésitez pas à nous contacter ou par MP !
 
 
 
 
Partenaires

Hébergement Web