~ubuntu-branches/ubuntu/saucy/autopilot/saucy-proposed

« back to all changes in this revision

Viewing changes to docs/tutorial/getting_started.rst

  • Committer: Package Import Robot
  • Author(s): Ubuntu daily release, Łukasz 'sil2100' Zemczak, Ubuntu daily release
  • Date: 2013-07-12 00:02:34 UTC
  • mfrom: (52.5.1) (58.1.9 saucy-proposed)
  • Revision ID: package-import@ubuntu.com-20130712000234-gsrwbjke7oi0yfxt
Tags: 1.3.1+13.10.20130712-0ubuntu1
[ Łukasz 'sil2100' Zemczak ]
* I don't like it, but it's the easiest way - make autopilot-touch
  Arch: any and make the python-ubuntu-platform-api [armhf] dependent.

[ Ubuntu daily release ]
* Automatic snapshot from revision 265

Show diffs side-by-side

added added

removed removed

Lines of Context:
206
206
A Test with Interaction
207
207
=======================
208
208
 
209
 
.. TODO: Add a second test, one that adds some keyboard / mouse interaction.
 
209
Now lets take a look at some simple tests with some user interaction. First, update the test application with some input and output controls::
 
210
 
 
211
        #!/usr/bin/env python
 
212
 
 
213
        from PyQt4 import QtGui
 
214
        from sys import argv
 
215
 
 
216
        class AutopilotHelloWorld(QtGui.QWidget):
 
217
            def __init__(self):
 
218
                super(AutopilotHelloWorld, self).__init__()
 
219
 
 
220
                self.hello = QtGui.QPushButton("Hello")
 
221
                self.hello.clicked.connect(self.say_hello)
 
222
 
 
223
                self.goodbye = QtGui.QPushButton("Goodbye")
 
224
                self.goodbye.clicked.connect(self.say_goodbye)
 
225
 
 
226
                self.response = QtGui.QLabel("Response: None")
 
227
 
 
228
                grid = QtGui.QGridLayout()
 
229
                grid.addWidget(self.hello, 0, 0)
 
230
                grid.addWidget(self.goodbye, 0, 1)
 
231
                grid.addWidget(self.response, 1, 0, 1, 2)
 
232
                self.setLayout(grid)
 
233
                self.show()
 
234
                self.setWindowTitle("Hello World")
 
235
 
 
236
            def say_hello(self):
 
237
                self.response.setText('Response: Hello')
 
238
 
 
239
            def say_goodbye(self):
 
240
                self.response.setText('Response: Goodbye')
 
241
 
 
242
 
 
243
        def main():
 
244
            app = QtGui.QApplication(argv)
 
245
            ahw = AutopilotHelloWorld()
 
246
            app.exec_()
 
247
 
 
248
        if __name__ == '__main__':
 
249
                main()
 
250
 
 
251
We've reorganized the application code into a class to make the event handling easier. Then we added two input controls, the ``hello`` and ``goodbye`` buttons and an output control, the ``response`` label.
 
252
 
 
253
The operation of the application is still very trivial, but now we can test that it actually does something in response to user input. Clicking either of the two buttons will cause the response text to change. Clicking the ``Hello`` button should result in ``Response: Hello`` while clicking the ``Goodbye`` button should result in ``Response: Goodbye``.
 
254
 
 
255
Since we're adding a new category of tests, button response tests, we should organize them into a new class. Our tests module now looks like::
 
256
 
 
257
        from autopilot.testcase import AutopilotTestCase
 
258
        from os.path import abspath, dirname, join
 
259
        from testtools.matchers import Equals
 
260
 
 
261
        from autopilot.input import Mouse
 
262
        from autopilot.matchers import Eventually
 
263
 
 
264
        class HelloWorldTestBase(AutopilotTestCase):
 
265
 
 
266
            def launch_application(self):
 
267
                """Work out the full path to the application and launch it.
 
268
 
 
269
                This is necessary since our test application will not be in $PATH.
 
270
 
 
271
                :returns: The application proxy object.
 
272
 
 
273
                """
 
274
                full_path = abspath(join(dirname(__file__), '..', '..', 'testapp.py'))
 
275
                return self.launch_test_application(full_path, app_type='qt')
 
276
 
 
277
 
 
278
        class MainWindowTitleTests(HelloWorldTestBase):
 
279
 
 
280
            def test_main_window_title_string(self):
 
281
                """The main window title must be 'Hello World'."""
 
282
                app_root = self.launch_application()
 
283
                main_window = app_root.select_single('AutopilotHelloWorld')
 
284
 
 
285
                self.assertThat(main_window.windowTitle, Equals("Hello World"))
 
286
 
 
287
 
 
288
        class ButtonResponseTests(HelloWorldTestBase):
 
289
 
 
290
            def test_hello_response(self):
 
291
                """The response text must be 'Response: Hello' after a Hello click."""
 
292
                app_root = self.launch_application()
 
293
                response = app_root.select_single('QLabel')
 
294
                hello = app_root.select_single('QPushButton', text='Hello')
 
295
 
 
296
                self.mouse.click_object(hello)
 
297
 
 
298
                self.assertThat(response.text, Eventually(Equals('Response: Hello')))
 
299
 
 
300
            def test_goodbye_response(self):
 
301
                """The response text must be 'Response: Goodbye' after a Goodbye
 
302
                click."""
 
303
                app_root = self.launch_application()
 
304
                response = app_root.select_single('QLabel')
 
305
                goodbye = app_root.select_single('QPushButton', text='Goodbye')
 
306
 
 
307
                self.mouse.click_object(goodbye)
 
308
 
 
309
                self.assertThat(response.text, Eventually(Equals('Response: Goodbye')))
 
310
 
 
311
In addition to the new class, ``ButtonResponseTests``, you'll notice a few other changes. First, two new import lines were added to support the new tests. Next, the existing ``MainWindowTitleTests`` class was refactored to subclass from a base class, ``HelloWorldTestBase``. The base class contains the ``launch_application`` method which is used for all test cases. Finally, the object type of the main window changed from ``QMainWindow`` to ``AutopilotHelloWorld``. The change in object type is a result of our test application being refactored into a class called ``AutopilotHelloWorld``.
 
312
 
 
313
.. otto:: **Be careful when identifing user interface controls**
 
314
 
 
315
        Notice that our simple refactoring of the test application forced a change to the test for the main window. When developing application code, put a little extra thought into how the user interface controls will be identified in the tests. Identify objects with attributes that are likely to remain constant as the application code is developed.
 
316
 
 
317
The ``ButtonResponseTests`` class adds two new tests, one for each input control. Each test identifies the user interface controls that need to be used, performs a single, specific action, and then verifies the outcome. In ``test_hello_response``, we first identify the ``QLabel`` control which contains the output we need to check. We then identify the ``Hello`` button. As the application has two ``QPushButton`` controls, we must further refine the ``select_single`` call by specifing an additional property. In this case, we use the button text. Next, an input action is triggered by instructing the ``mouse`` to click the ``Hello`` button. Finally, the test asserts that the response label text matches the expected string. The second test repeats the same process with the ``Goodbye`` button.
210
318
 
211
319
The Eventually Matcher
212
320
======================
213
321
 
214
 
.. TODO: Discuss the issues with running tests & application in separate processes, and how the Eventually matcher helps us overcome these problems. Cover the various ways the matcher can be used.
 
322
Notice that in the ButtonResponseTests tests above, the autopilot method :class:`~autopilot.matchers.Eventually` is used in the assertion. This allows the assertion to be retried continuously until it either becomes true, or times out (the default timout is 10 seconds). This is necessary because the application and the autopilot tests run in different processes. Autopilot could test the assert before the application has completed its action. Using :class:`~autopilot.matchers.Eventually` allows the application to complete its action without having to explicitly add delays to the tests.
 
323
 
 
324
.. otto:: **Use Eventually when asserting any user interface condition**
 
325
 
 
326
        You may find that when running tests, the application is often ready with the outcome by the time autopilot is able to test the assertion without using :class:`~autopilot.matchers.Eventually`. However, this may not always be true when running your test suite on different hardware.
 
327
 
 
328
.. TODO: Continue to discuss the issues with running tests & application in separate processes, and how the Eventually matcher helps us overcome these problems. Cover the various ways the matcher can be used.