Merge lp:~nskaggs/autopilot/apdocs-tutorial into lp:autopilot

Proposed by Nicholas Skaggs
Status: Work in progress
Proposed branch: lp:~nskaggs/autopilot/apdocs-tutorial
Merge into: lp:autopilot
Diff against target: 534 lines (+197/-246)
2 files modified
docs/tutorial/getting_started.rst (+196/-245)
docs/tutorial/what_is_autopilot.rst (+1/-1)
To merge this branch: bzr merge lp:~nskaggs/autopilot/apdocs-tutorial
Reviewer Review Type Date Requested Status
Autopilot Hackers Pending
Review via email: mp+247218@code.launchpad.net

Commit message

Rebase autopilot tutorial to use qml

Description of the change

Rebase autopilot tutorial to use qml

To post a comment you must log in.
524. By Nicholas Skaggs

remerge trunk

525. By Nicholas Skaggs

rebase trunk

526. By Nicholas Skaggs

wip

527. By Nicholas Skaggs

rebase trunk

528. By Nicholas Skaggs

rebase trunk

529. By Nicholas Skaggs

fix tutorial

Unmerged revisions

529. By Nicholas Skaggs

fix tutorial

528. By Nicholas Skaggs

rebase trunk

527. By Nicholas Skaggs

rebase trunk

526. By Nicholas Skaggs

wip

525. By Nicholas Skaggs

rebase trunk

524. By Nicholas Skaggs

remerge trunk

523. By Nicholas Skaggs

wip

522. By Nicholas Skaggs

wip; initial tutorial work

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'docs/tutorial/getting_started.rst'
--- docs/tutorial/getting_started.rst 2015-01-25 23:08:51 +0000
+++ docs/tutorial/getting_started.rst 2015-01-27 20:14:32 +0000
@@ -3,78 +3,47 @@
33
4This document contains everything you need to know to write your first autopilot test. It covers writing several simple tests for a sample Qt5/Qml application. However, it's important to note that nothing in this tutorial is specific to Qt5/Qml, and will work equally well with any other kind of application.4This document contains everything you need to know to write your first autopilot test. It covers writing several simple tests for a sample Qt5/Qml application. However, it's important to note that nothing in this tutorial is specific to Qt5/Qml, and will work equally well with any other kind of application.
55
6Files and Directories6Prerequisites
7=====================7=============
88For the tutorial you will need to ensure you have installed autopilot. See :ref:`installing_autopilot` for installation instructions.
9Your autopilot test suite will grow to several files, possibly spread across several directories. We recommend that you follow this simple directory layout::9
1010In addition, if you wish to follow along directly with the tutorial you should install the ubuntu sdk. See `developer.ubuntu.com <https://developer.ubuntu.com/en/start/ubuntu-sdk/installing-the-sdk/>`_ for installation instructions. Since you can apply the tutorial to any application introspectable by autopilot this step is optional.
11 autopilot/
12 autopilot/<projectname>/
13 autopilot/<projectname>/tests/
14
15The ``autopilot`` folder can be anywhere within your project's source tree. It will likely contain a `setup.py <http://docs.python.org/3/distutils/setupscript.html>`_ file.
16
17The ``autopilot/<projectname>/`` folder is the base package for your autopilot tests. This folder, and all child folders, are python packages, and so must contain an `__init__.py file <http://docs.python.org/3/tutorial/modules.html#packages>`_. If you ever find yourself writing custom proxy classes (This is an advanced topic, and is covered here: :ref:`custom_proxy_classes`), they should be imported from this top-level package.
18
19Each test file should be named ``test_<component>.py``, where *<component>* is the logical component you are testing in that file. Test files must be written in the ``autopilot/<projectname>/tests/`` folder.
2011
21A Minimal Test Case12A Minimal Test Case
22+++++++++++++++++++13+++++++++++++++++++
2314
24Autopilot tests follow a similar pattern to other python test libraries: you must declare a class that derives from :class:`~autopilot.testcase.AutopilotTestCase`. A minimal test case looks like this::15Autopilot tests follow a similar pattern to other python test libraries: you must declare a class that derives from :class:`~autopilot.testcase.AutopilotTestCase`. The *AutopilotTestCase* class contains methods to launch and introspect and application, along with the ability to make assertions. Thus, a minimal test case looks like this::
2516
26 from autopilot.testcase import AutopilotTestCase17 from autopilot.testcase import AutopilotTestCase
2718
2819
29 class MyTests(AutopilotTestCase):20 class MyTests(AutopilotTestCase):
3021
31 def test_something(self):22 def test_something(self):
32 """An example test case that will always pass."""23 """An example test case that will always pass."""
33 self.assertTrue(True)24 self.assertTrue(True)
3425
35.. otto:: **Make your tests expressive!**26.. otto:: **Make your tests expressive!**
3627
37 It's important to make sure that your tests express your *intent* as clearly as possible. We recommend choosing long, descriptive names for test functions and classes (even breaking :pep:`8`, if you need to), and give your tests a detailed docstring explaining exactly what you are trying to test. For more detailed advice on this point, see :ref:`write-expressive-tests`28 It's important to make sure that your tests express your *intent* as clearly as possible. We recommend choosing long, descriptive names for test functions and classes (even breaking :pep:`8`, if you need to), and give your tests a detailed docstring explaining exactly what you are trying to test. For more detailed advice on this point, see :ref:`write-expressive-tests`
3829
39The Setup Phase
40===============
41
42Before each test is run, the ``setUp`` method is called. Test authors may override this method to run any setup that needs to happen before the test is run. However, care must be taken when using the ``setUp`` method: it tends to hide code from the test case, which can make your tests less readable. It is our recommendation, therefore, that you use this feature sparingly. A more suitable alternative is often to put the setup code in a separate function or method and call it from the test function.
43
44Should you wish to put code in a setup method, it looks like this:
45
46.. code-block:: python
47
48 from autopilot.testcase import AutopilotTestCase
49
50
51 class MyTests(AutopilotTestCase):
52
53 def setUp(self):
54 super(MyTests, self).setUp()
55 # This code gets run before every test!
56
57 def test_something(self):
58 """An example test case that will always pass."""
59 self.assertTrue(True)
60
61.. note::
62 Any action you take in the setup phase must be undone if it alters the system state. See :ref:`cleaning-up` for more details.
6330
64Starting the Application31Starting the Application
65++++++++++++++++++++++++32++++++++++++++++++++++++
6633
67At the start of your test, you need to tell autopilot to launch your application. To do this, call :meth:`~autopilot.testcase.AutopilotTestCase.launch_test_application`. The minimum required argument to this method is the application name or path. If you pass in the application name, autopilot will look in the current working directory, and then will search the :envvar:`PATH` environment variable. Otherwise, autopilot looks for the executable at the path specified. Positional arguments to this method are passed to the executable being launched.34At the start of your test, you need to tell autopilot to launch your application. To do this, call :meth:`~autopilot.testcase.AutopilotTestCase.launch_test_application`. The minimum required argument to this method is the application name or path. If you pass in the application name, autopilot will look in the current working directory, and then will search the :envvar:`PATH` environment variable. Otherwise, autopilot looks for the executable at the path specified. Positional arguments to this method are passed to the executable being launched. For example, we can launch our Qt5 qml application by launching qmlscene with the page to our qml file::
35
36self.launch_test_application('qmlscene', 'my_scene.qml')
6837
69Autopilot will try and guess what type of application you are launching, and therefore what kind of introspection libraries it should load. Sometimes autopilot will need some assistance however. For example, at the time of writing, autopilot cannot automatically detect the introspection type for python / Qt4 applications. In that case, a :class:`RuntimeError` will be raised. To provide autopilot with a hint as to which introspection type to load, you can provide the ``app_type`` keyword argument. For example::38Autopilot will try and guess what type of application you are launching, and therefore what kind of introspection libraries it should load. Sometimes autopilot will need some assistance however. For example, at the time of writing, autopilot cannot automatically detect the introspection type for python / Qt4 applications. In that case, a :class:`RuntimeError` will be raised. To provide autopilot with a hint as to which introspection type to load, you can provide the ``app_type`` keyword argument. For example::
7039
71 class MyTests(AutopilotTestCase):40 class MyTests(AutopilotTestCase):
7241
73 def test_python_qt4_application(self):42 def test_python_qt4_application(self):
74 self.app = self.launch_test_application(43 self.app = self.launch_test_application(
75 'my-pyqt4-app',44 'my-pyqt4-app',
76 app_type='qt'45 app_type='qt'
77 )46 )
7847
79See the documentation for :meth:`~autopilot.testcase.AutopilotTestCase.launch_test_application` for more details.48See the documentation for :meth:`~autopilot.testcase.AutopilotTestCase.launch_test_application` for more details.
8049
@@ -82,169 +51,151 @@
8251
83.. otto:: **What is a Proxy Object?**52.. otto:: **What is a Proxy Object?**
8453
85 Whenever you launch an application, autopilot gives you a "proxy object". These are instances of the :class:`~autopilot.introspection.ProxyBase` class, with all the data from your application mirrored in the proxy object instances. For example, if you have a proxy object for a push button class (say, ``QPushButton``, for example), the proxy object will have attribute to match every attribute in the class within your application. Autopilot automatically keeps the data in these instances up to date, so you can use them in your test assertions.54 Whenever you launch an application, autopilot gives you a "proxy object". These are instances of the :class:`~autopilot.introspection.ProxyBase` class, with all the data from your application mirrored in the proxy object instances. For example, if you have a proxy object for a push button class (say, ``QPushButton``, for example), the proxy object will have attribute to match every attribute in the class within your application. Autopilot automatically keeps the data in these instances up to date, so you can use them in your test assertions.
8655
87 User interfaces are made up of a tree of widgets, and autopilot represents these widgets as a tree of proxy objects. Proxy objects have a number of methods on them for selecting child objects in the introspection tree, so test authors can easily inspect the parts of the UI tree they care about.56 User interfaces are made up of a tree of widgets, and autopilot represents these widgets as a tree of proxy objects. Proxy objects have a number of methods on them for selecting child objects in the introspection tree, so test authors can easily inspect the parts of the UI tree they care about. Later in the tutorial using :meth:`~autopilot.introspection.ProxyBase.print_tree` and the vis tool (see :ref:`visualise_introspection_tree`) will show you ways to examine a UI tree.
57
58Testcase Layout
59===============
60
61Your autopilot test suite will grow to several files, possibly spread across several directories. We recommend that you follow this simple directory layout::
62
63 autopilot/
64 autopilot/<projectname>/
65 autopilot/<projectname>/tests/
66
67The ``autopilot`` folder can be anywhere within your project's source tree. It will likely contain a `setup.py <http://docs.python.org/3/distutils/setupscript.html>`_ file.
68
69The ``autopilot/<projectname>/`` folder is the base package for your autopilot tests. This folder, and all child folders, are python packages, and so must contain an `__init__.py file <http://docs.python.org/3/tutorial/modules.html#packages>`_. If you ever find yourself writing custom proxy classes (This is an advanced topic, and is covered here: :ref:`custom_proxy_classes`), they should be imported from this top-level package.
70
71Each test file should be named ``test_<component>.py``, where *<component>* is the logical component you are testing in that file. Test files must be written in the ``autopilot/<projectname>/tests/`` folder.
72
73So for example, here is a minimal layout following the above recommendations::
74
75 autopilot/exampleproject/__init__.py
76 autopilot/exampleproject/tests/__init__.py
77 autopilot/exampleproject/tests/test_example_component.py
78
79In the example above the **test_example_component.py** file would contain the actual testcases.
80
8881
89A Simple Test82A Simple Test
90=============83=============
9184
92To demonstrate the material covered so far, this selection will outline a simple application, and a single test for it. Instead of testing a third-party application, we will write the simplest possible application in Python and Qt4. The application, named 'testapp.py', is listed below::85To demonstrate the material covered so far, this selection will outline a simple application and a single test for it. Instead of testing a third-party application, we will write the simplest possible application using Qt5 and qml. The application, named 'testapp.qml', is listed below::
9386
94 #!/usr/bin/env python87 import QtQuick 2.0
9588 import Ubuntu.Components 1.1
96 from PyQt4 import QtGui89
97 from sys import argv90 MainView {
9891
99 def main():92 width: units.gu(100)
100 app = QtGui.QApplication(argv)93 height: units.gu(75)
101 win = QtGui.QMainWindow()94
102 win.show()95 Page {
103 win.setWindowTitle("Hello World")96 title: "Hello World"
104 app.exec_()97 }
10598 }
106 if __name__ == '__main__':99
107 main()100
108101As you can see, this is a trivial application, but it serves our purpose. We will write a single autopilot test that asserts that the title of the main window is equal to the string "Hello World". Our test file is named "test_page.py", and contains the following code::
109As you can see, this is a trivial application, but it serves our purpose. We will write a single autopilot test that asserts that the title of the main window is equal to the string "Hello World". Our test file is named "test_window.py", and contains the following code::102
110103 from autopilot.testcase import AutopilotTestCase
111 from autopilot.testcase import AutopilotTestCase104 from testtools.matchers import Equals
112 from os.path import abspath, dirname, join105
113 from testtools.matchers import Equals106
114107 class TestPage(AutopilotTestCase):
115 class MainWindowTitleTests(AutopilotTestCase):108
116109 def launch_application(self):
117 def launch_application(self):110 return self.launch_test_application(
118 """Work out the full path to the application and launch it.111 'qmlscene',
119112 '../../testapp.qml',
120 This is necessary since our test application will not be in $PATH.113 app_type='qt')
121114
122 :returns: The application proxy object.115 def test_page_title(self):
123116 """The main page title must be 'Hello World'."""
124 """117 app = self.launch_application()
125 full_path = abspath(join(dirname(__file__), '..', '..', 'testapp.py'))118 page = app.select_single('Page11')
126 return self.launch_test_application(full_path, app_type='qt')119
127120 self.assertThat(page.title, Equals("Hello World"))
128 def test_main_window_title_string(self):121
129 """The main window title must be 'Hello World'."""122
130 app_root = self.launch_application()123Note that we have made the test method as readable as possible by hiding the complexities of finding the full path to the application we want to test (passing ../../testapp.qml). Of course, if you can guarantee that the application is in :envvar:`PATH`, then this step becomes a lot simpler.
131 main_window = app_root.select_single('QMainWindow')
132
133 self.assertThat(main_window.windowTitle, Equals("Hello World"))
134
135
136Note that we have made the test method as readable as possible by hiding the complexities of finding the full path to the application we want to test. Of course, if you can guarantee that the application is in :envvar:`PATH`, then this step becomes a lot simpler.
137124
138The entire directory structure looks like this::125The entire directory structure looks like this::
139126
140 ./example/__init__.py127 ./testapp.qml
141 ./example/tests/__init__.py128 ./autopilotqml/__init__.py
142 ./example/tests/test_window.py129 ./autopilotqml/tests/__init__.py
143 ./testapp.py130 ./autopilotqml/tests/test_page.py
144131
145The ``__init__.py`` files are empty, and are needed to make these directories importable by python.132The ``__init__.py`` files are empty, and are needed to make these directories importable by python.
146133
147Running Autopilot134Running Autopilot
148+++++++++++++++++135+++++++++++++++++
149136
150From the root of this directory structure, we can ask autopilot to list all the tests it can find::137From the root of this directory structure, we can ask autopilot to list all the tests it can find. Note that we use our python package name, which is the same as the root folder of the tests::
151138
152 $ autopilot3 list example139 $ autopilot3 list autopilotqml
153 Loading tests from: /home/thomi/code/canonical/autopilot/example_test140 Loading tests from: /home/nskaggs/projects/ubuntutouch/autopilotqml/tests/autopilot
154141
155 example.tests.test_window.MainWindowTitleTests.test_main_window_title_string142 autopilotqml.test_main.TestPage.test_page_title
156143
157144 1 total tests.
158 1 total tests.
159145
160Note that on the first line, autopilot will tell you where it has loaded the test definitions from. Autopilot will look in the current directory for a python package that matches the package name specified on the command line. If it does not find any suitable packages, it will look in the standard python module search path instead.146Note that on the first line, autopilot will tell you where it has loaded the test definitions from. Autopilot will look in the current directory for a python package that matches the package name specified on the command line. If it does not find any suitable packages, it will look in the standard python module search path instead.
161147
162To run our test, we use the autopilot 'run' command::148To run our test, we use the autopilot 'run' command::
163149
164 $ autopilot3 run example150 $ autopilot3 run autopilotqml
165 Loading tests from: /home/thomi/code/canonical/autopilot/example_test151 Loading tests from: /home/nskaggs/projects/ubuntutouch/autopilotqml/tests/autopilot
166152
167 Tests running...153 Tests running...
168154
169 Ran 1 test in 2.342s155 Ran 1 test in 2.503s
170 OK156 OK
171157
172You will notice that the test application launches, and then dissapears shortly afterwards. Since this test doesn't manipulate the application in any way, this is a rather boring test to look at. If you ever want more output from the run command, you may specify the '-v' flag::158You will notice that the test application launches, and then dissapears shortly afterwards. Since this test doesn't manipulate the application in any way, this is a rather boring test to look at. If you ever want more output from the run command, you may specify the '-v' flag::
173159
174 $ autopilot3 run -v example160 $ autopilot3 run -v autopilotqml
175 Loading tests from: /home/thomi/code/canonical/autopilot/example_test161 12:07:50.313 INFO run:235 - Autopilot Source Version: 1.5.0
176162 Autopilot Package Version: 1.5.0+15.04.20141031-0ubuntu1
177 Tests running...163 Loading tests from: /home/nskaggs/projects/ubuntutouch/autopilotqml/tests/autopilot
178 13:41:11.614 INFO globals:49 - ************************************************************164
179 13:41:11.614 INFO globals:50 - Starting test example.tests.test_window.MainWindowTitleTests.test_main_window_title_string165 Tests running...
180 13:41:11.693 INFO __init__:136 - Launching process: ['/home/thomi/code/canonical/autopilot/example_test/testapp.py', '-testability']166 12:07:50.323 INFO _logging:40 - ************************************************************
181 13:41:11.699 INFO __init__:169 - Looking for autopilot interface for PID 12013 (and children)167 12:07:50.323 INFO _logging:41 - Starting test autopilotqml.test_main.TestPage.test_page_title
182 13:41:11.727 WARNING __init__:185 - Caught exception while searching for autopilot interface: 'DBusException("Could not get PID of name 'org.freedesktop.DBus': no such name",)'168 12:07:50.528 INFO _launcher:373 - Attempting to launch application 'qmlscene' with arguments '../../testapp.qml' as a normal process
183 13:41:12.773 WARNING __init__:185 - Caught exception while searching for autopilot interface: 'DBusException("Could not get PID of name 'org.freedesktop.DBus': no such name",)'169 12:07:50.532 INFO _launcher:431 - Launching process: ['/usr/bin/qmlscene', '-testability', '../../testapp.qml']
184 13:41:12.848 WARNING __init__:185 - Caught exception while searching for autopilot interface: 'RuntimeError("Could not find Autopilot interface on DBus backend '<session bus :1.5967 /com/canonical/Autopilot/Introspection>'",)'170 12:07:51.739 INFO _launcher:544 - waiting for process to exit.
185 13:41:12.852 WARNING __init__:185 - Caught exception while searching for autopilot interface: 'RuntimeError("Could not find Autopilot interface on DBus backend '<session bus :1.5968 /com/canonical/Autopilot/Introspection>'",)'171 12:07:51.739 INFO _launcher:567 - Killing process 31240
186 13:41:12.863 WARNING dbus:464 - Generating introspection instance for type 'Root' based on generic class.172 12:07:51.785 INFO testresult:44 - OK: autopilotqml.test_main.TestPage.test_page_title
187 13:41:12.864 DEBUG dbus:338 - Selecting objects of type QMainWindow with attributes: {}173
188 13:41:12.871 WARNING dbus:464 - Generating introspection instance for type 'QMainWindow' based on generic class.174 Ran 1 test in 1.462s
189 13:41:12.886 INFO testcase:380 - waiting for process to exit.175 OK
190 13:41:13.983 INFO testresult:35 - OK: example.tests.test_window.MainWindowTitleTests.test_main_window_title_string
191
192 Ran 1 test in 2.370s
193 OK
194176
195You may also specify '-v' twice for even more output (this is rarely useful for test authors however).177You may also specify '-v' twice for even more output (this is rarely useful for test authors however).
196178
197Both the 'list' and 'run' commands take a test id as an argument. You may be as generic, or as specific as you like. In the examples above, we will list and run all tests in the 'example' package (i.e.- all tests), but we could specify a more specific run criteria if we only wanted to run some of the tests. For example, to only run the single test we've written, we can execute::179Both the 'list' and 'run' commands take a test id as an argument. You may be as generic, or as specific as you like. In the examples above, we will list and run all tests in the 'autopilotqml' package (i.e.- all tests), but we could specify a more specific run criteria if we only wanted to run some of the tests. For example, to only run the single test we've written, we can execute::
198180
199 $ autopilot3 run example.tests.test_window.MainWindowTitleTests.test_main_window_title_string181 $ autopilot3 run autopilotqml.test_main.TestPage.test_page_title
182
183or run just the tests for TestPage::
184
185 $ autopilot3 run autopilotqml.test_main.TestPage.test_page_title
186
187or run all the tests inside test_main::
188
189 $ autopilot3 run autopilotqml.test_main
200190
201.. _tut_test_with_interaction:191.. _tut_test_with_interaction:
202192
203A Test with Interaction193A Test with Interaction
204=======================194=======================
205195
206Now lets take a look at some simple tests with some user interaction. First, update the test application with some input and output controls::196Now lets take a look at some simple tests with some user interaction. First, update the test application with some buttons:
207197
208 #!/usr/bin/env python198
209 # File: testapp.py
210
211 from PyQt4 import QtGui
212 from sys import argv
213
214 class AutopilotHelloWorld(QtGui.QWidget):
215 def __init__(self):
216 super(AutopilotHelloWorld, self).__init__()
217
218 self.hello = QtGui.QPushButton("Hello")
219 self.hello.clicked.connect(self.say_hello)
220
221 self.goodbye = QtGui.QPushButton("Goodbye")
222 self.goodbye.clicked.connect(self.say_goodbye)
223
224 self.response = QtGui.QLabel("Response: None")
225
226 grid = QtGui.QGridLayout()
227 grid.addWidget(self.hello, 0, 0)
228 grid.addWidget(self.goodbye, 0, 1)
229 grid.addWidget(self.response, 1, 0, 1, 2)
230 self.setLayout(grid)
231 self.show()
232 self.setWindowTitle("Hello World")
233
234 def say_hello(self):
235 self.response.setText('Response: Hello')
236
237 def say_goodbye(self):
238 self.response.setText('Response: Goodbye')
239
240
241 def main():
242 app = QtGui.QApplication(argv)
243 ahw = AutopilotHelloWorld()
244 app.exec_()
245
246 if __name__ == '__main__':
247 main()
248199
249We'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.200We'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.
250201
@@ -252,65 +203,65 @@
252203
253Since 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::204Since 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::
254205
255 from autopilot.testcase import AutopilotTestCase206 from autopilot.testcase import AutopilotTestCase
256 from os.path import abspath, dirname, join207 from os.path import abspath, dirname, join
257 from testtools.matchers import Equals208 from testtools.matchers import Equals
258209
259 from autopilot.input import Mouse210 from autopilot.input import Mouse
260 from autopilot.matchers import Eventually211 from autopilot.matchers import Eventually
261212
262 class HelloWorldTestBase(AutopilotTestCase):213 class HelloWorldTestBase(AutopilotTestCase):
263214
264 def launch_application(self):215 def launch_application(self):
265 """Work out the full path to the application and launch it.216 """Work out the full path to the application and launch it.
266217
267 This is necessary since our test application will not be in $PATH.218 This is necessary since our test application will not be in $PATH.
268219
269 :returns: The application proxy object.220 :returns: The application proxy object.
270221
271 """222 """
272 full_path = abspath(join(dirname(__file__), '..', '..', 'testapp.py'))223 full_path = abspath(join(dirname(__file__), '..', '..', 'testapp.py'))
273 return self.launch_test_application(full_path, app_type='qt')224 return self.launch_test_application(full_path, app_type='qt')
274225
275226
276 class MainWindowTitleTests(HelloWorldTestBase):227 class MainWindowTitleTests(HelloWorldTestBase):
277228
278 def test_main_window_title_string(self):229 def test_main_window_title_string(self):
279 """The main window title must be 'Hello World'."""230 """The main window title must be 'Hello World'."""
280 app_root = self.launch_application()231 app_root = self.launch_application()
281 main_window = app_root.select_single('AutopilotHelloWorld')232 main_window = app_root.select_single('AutopilotHelloWorld')
282233
283 self.assertThat(main_window.windowTitle, Equals("Hello World"))234 self.assertThat(main_window.windowTitle, Equals("Hello World"))
284235
285236
286 class ButtonResponseTests(HelloWorldTestBase):237 class ButtonResponseTests(HelloWorldTestBase):
287238
288 def test_hello_response(self):239 def test_hello_response(self):
289 """The response text must be 'Response: Hello' after a Hello click."""240 """The response text must be 'Response: Hello' after a Hello click."""
290 app_root = self.launch_application()241 app_root = self.launch_application()
291 response = app_root.select_single('QLabel')242 response = app_root.select_single('QLabel')
292 hello = app_root.select_single('QPushButton', text='Hello')243 hello = app_root.select_single('QPushButton', text='Hello')
293244
294 self.mouse.click_object(hello)245 self.mouse.click_object(hello)
295246
296 self.assertThat(response.text, Eventually(Equals('Response: Hello')))247 self.assertThat(response.text, Eventually(Equals('Response: Hello')))
297248
298 def test_goodbye_response(self):249 def test_goodbye_response(self):
299 """The response text must be 'Response: Goodbye' after a Goodbye250 """The response text must be 'Response: Goodbye' after a Goodbye
300 click."""251 click."""
301 app_root = self.launch_application()252 app_root = self.launch_application()
302 response = app_root.select_single('QLabel')253 response = app_root.select_single('QLabel')
303 goodbye = app_root.select_single('QPushButton', text='Goodbye')254 goodbye = app_root.select_single('QPushButton', text='Goodbye')
304255
305 self.mouse.click_object(goodbye)256 self.mouse.click_object(goodbye)
306257
307 self.assertThat(response.text, Eventually(Equals('Response: Goodbye')))258 self.assertThat(response.text, Eventually(Equals('Response: Goodbye')))
308259
309In 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``.260In 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``.
310261
311.. otto:: **Be careful when identifing user interface controls**262.. otto:: **Be careful when identifing user interface controls**
312263
313 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.264 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.
314265
315The ``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.266The ``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.
316267
@@ -321,6 +272,6 @@
321272
322.. otto:: **Use Eventually when asserting any user interface condition**273.. otto:: **Use Eventually when asserting any user interface condition**
323274
324 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.275 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.
325276
326.. 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.277.. 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.
327278
=== modified file 'docs/tutorial/what_is_autopilot.rst'
--- docs/tutorial/what_is_autopilot.rst 2014-05-15 00:45:08 +0000
+++ docs/tutorial/what_is_autopilot.rst 2015-01-27 20:14:32 +0000
@@ -11,7 +11,7 @@
11Where is Autopilot used?11Where is Autopilot used?
12########################12########################
1313
14Autopilot was designed to test the `Unity 3D <http://unity.ubuntu.com/>`_ shell. However, since then it has been used to test a number of other applications, including:14Autopilot was originally designed to test the `Unity 3D <http://unity.ubuntu.com/>`_ shell. However, since then it has been used to test a number of other applications, including:
1515
16* Core Ubuntu GUI applications.16* Core Ubuntu GUI applications.
17* Mobile phone applications for the Ubuntu Phone & Ubuntu Tablet.17* Mobile phone applications for the Ubuntu Phone & Ubuntu Tablet.

Subscribers

People subscribed via source and target branches