Difference between revisions of "Automated Testing"

From Rosalab Wiki
Jump to: navigation, search
(Prepared the common part of the docs)
(Added the first tutorial)
Line 62: Line 62:
  
 
== Creating the Tests - Tutorial #1 ==
 
== Creating the Tests - Tutorial #1 ==
 +
 +
Here we assume the test developer has at least basic skills in Python (to obtain these skills, it would be more than enough to study the official tutorial: http://docs.python.org/2.7/tutorial/index.html)
 +
 +
Let us prepare a test that launches Firefox, opens a new tab and goes to a particular web site. Everything is done here on ROSA Desktop Fresh with KDE.
 +
 +
First, create a file, say, ''test_firefox.py'' and ''images'' subdirectory in the same directory. ''images'' will contain the images the test will use.
 +
 +
Suppose the desktop looks as follows before the test starts:
 +
 +
[[File:rosa_autotest_scr1.png|600px]]
 +
 +
To launch Firefox, it is enough to click its icon on the panel. For the test to be able to do so automatically, let us take a screenshot and extract the icon from there: [[File:rosa_autotest_ff_start.png]]. It is often desirable to take only the inner part of that image ([[File:rosa_autotest_ff_start_inner.png]]), to avoid depending on the background of the icon.
 +
 +
Save that image as ''ff-start.png'' in ''images'' subdirectory.
 +
 +
Now open ''test_firefox.py'' in a text editor of your choice and write the following there:
 +
 +
<pre>
 +
import time
 +
import rosatest
 +
 +
class TestFirefox(rosatest.TestCaseInstalled):
 +
    """Tests for Firefox"""
 +
 +
    @rosatest.imagebased
 +
    def test_ff(self):
 +
        """A simple test"""
 +
        self.load_images("images")
 +
 +
        self.click("ff-start")
 +
</pre>
 +
 +
The needed modules are imported at the beginning, then <code>TestFirefox</code> is defined as a class derived from <code>rosatest.TestCaseInstalled</code>.
 +
 +
The derived classes of <code>rosatest.TestCaseInstalled</code> are used to test software on the installed system, the ones derived from <code>rosatest.TestCaseLive</code> - on the system booted in Live mode.
 +
 +
As usual in the development of the tests based on [http://docs.python.org/2.7/library/unittest.html Python unittest], we should define one or more functions (test methods) in the class, each performing a test as a part of the test case. In this tutorial, it is <code>test_ff()</code>.
 +
 +
The test functions that rely on image recognition and emulation of the user actions should be marked with  <code>@rosatest.imagebased</code> decorator.
 +
 +
<code>self.load_images("images")</code> loads the images to be used by the test from the specified directory (''images'' that has been created earlier).
 +
 +
<code>self.click("ff-start")</code> looks for the image ''images/ff-start.png'' on the screen and clicks the left mouse button at the center of the found image. If it does not find the image, an exception will be raised and the test will fail.
 +
 +
Now we can execute the test and check that it actually launches Firefox (assuming Firefox is not running yet).
 +
The easiest way is perhaps to use ''local.py'' script. The results of the test will be placed to ''results'' subdirectory of the current directory in this case.
 +
 +
<pre>
 +
python /home/user/rosa-autotest/rosatest/local.py <path to test_firefox.py>
 +
</pre>
 +
 +
Firefox should start as a result:
 +
 +
[[File:rosa_autotest_scr2.png|600px]]
 +
 +
The following will be printed to the console:
 +
<pre>
 +
== Starting test test_firefox.TestFirefox.test_ff ==
 +
 +
Ran 1 test
 +
OK
 +
</pre>
 +
 +
''Note. Each test has an ID in the form '''file_name.class_name.function_name'''. When executed with local.py, the test will save its results to '''results/file_name/class_name/function_name/''' subdirectory of the current directory. Subdirectory '''tmp/file_name/class_name/function_name/''' is used to store the temporary files during the execution of the test.''
 +
 +
At the moment, the test only launches Firefox but does not check the browser has started successfully. We can try, for example, to check that "Home" button ([[File:rosa_autotest_ff_home_button.png]]) has appeared.
 +
 +
Take a shot of the appropriate part of the screen and save the image of that button as ''images/ff-home-button.png''. Then add the following to <code>test_ff()</code>:
 +
 +
<pre>
 +
        self.wait("ff-home-button")
 +
</pre>
 +
 +
<code>wait()</code> waits till the given image appears on the screen. If the image does show up, the execution of the test continues. If the given amount of time (30 seconds by default) has passed but the image has not appeared, <code>wait()</code> raises an exception, the test ends and is marked as failed.
 +
 +
The maximum amount of time to wait for an image can be set in <code>timeout</code> argument, the interval between consecutive checks - in <code>interval</code>, for example:
 +
 +
<pre>
 +
        self.wait("ff-home-button", timeout=120, interval=5)
 +
</pre>
 +
 +
<code>timeout</code> is 0, <code>wait()</code> will not wait at all but will rather check if the image is present immediately.
 +
 +
If the image has not appeared so far, the test will end with an error:
 +
 +
<pre>
 +
== Starting test test_firefox.TestFirefox.test_ff ==
 +
 +
==============================
 +
FAIL: test_ff (test_firefox.TestFirefox)
 +
A simple test
 +
------------------------------
 +
Traceback (most recent call last):
 +
  File "/usr/lib/python2.7/site-packages/rosatest/test.py", line 349, in internal
 +
    func(self, *args, **kwargs)
 +
  File "/home/eugene/work/tutorial/test_firefox.py", line 13, in test_ff
 +
    self.wait("ff-home-button", timeout=1)
 +
  File "/usr/lib/python2.7/site-packages/rosatest/test.py", line 299, in wait
 +
    match, "Failed to find an image on the screen: " + image)
 +
AssertionError: Failed to find an image on the screen: ff-home-button
 +
 +
Ran 1 test
 +
FAILED (failures=1)
 +
</pre>
 +
 +
When a test ends with an error, a screenshot will be taken at the time of failure and saved to the results directory of the test as 'scr_failure.png'. This may help analyze what has gone wrong. The log of the test (''test.log'' in the results directory) may also contain information useful when analyzing the results, including the messages output by that test.
 +
 +
''Note. The system uses the standard assertion methods from Python unittest. TestCase classes from rosatest module are derived from unittest.TestCase, so the tests may use the facilities from unittest directly.''
 +
 +
Now let the test open a new tab by clicking at [[File:rosa_autotest_ff_new_tab.png]] (save this image as ''images/ff-new-tab.png''), and enter "www.rosalab.com" there. To achieve this, add the following to <code>test_ff()</code>:
 +
 +
<pre>
 +
        self.click("ff-new-tab")
 +
 +
        # Wait a little for the tab to open.
 +
        time.sleep(1)
 +
 +
        self.type_string("www.rosalab.com")
 +
</pre>
 +
 +
What remains is to press "Enter" and wait until Firefox opens the needed site:
 +
 +
[[File:rosa_autotest_rosa_logo.png]]
 +
 +
Code:
 +
 +
<pre>
 +
        self.press_key("enter")
 +
        self.wait("rosa-logo")
 +
</pre>
 +
 +
"www.rosalab.com" should open as a result:
 +
 +
[[File:rosa_autotest_scr3.png|600px]]
 +
 +
The test may close Firefox now. To do this, let it press "alt-f4":
 +
 +
<pre>
 +
        self.press_key("alt-f4")
 +
</pre>
 +
 +
''Note. press_key() can be used to emulate pressing of both single keys ("esc", "f4", etc.) and the combinations of keys. The combinations are written with a hyphen as a separator: "ctrl-alt-v", "shift-f7", "ctrl-a" and so on. The names of the keys used by the testing system are listed here: http://en.wikibooks.org/wiki/QEMU/Monitor#sendkey_keys. In addition to that list, there are some handy aliases: "ret" and "enter" can be used for the "Enter" key, "del" is an alias for "delete", "ins" - for "insert".''
 +
 +
One should take into account that Firefox may (or may not) issue a warning about closing multiple tabs:
 +
 +
[[File:rosa_autotest_scr4.png|600px]]
 +
 +
The test may check, for example, whether "Close tabs" button ([[File:rosa_autotest_ff_close_tabs.png]], ''images/ff-close-tabs.png'') appears on the screen and if it does, click it:
 +
 +
<pre>
 +
        button = self.find("ff-close-tabs", timeout=5)
 +
        if button:
 +
            self.click(button)
 +
</pre>
 +
 +
<code>find()</code> is similar to <code>wait()</code> with a single significant difference. If the image is not found on the screen, <code>find()</code> returns <code>None</code> but does not raise exceptions. The test is not considered failed.
 +
 +
If the image is found, both <code>find()</code> and <code>wait()</code> return an object that can be passed to <code>click()</code>, <code>right_click()</code>, <code>double_click()</code> and the like instead of the name of an image which is demonstrated above.
 +
 +
So, here is the complete test:
 +
 +
<pre>
 +
import time
 +
import rosatest
 +
 +
class TestFirefox(rosatest.TestCaseInstalled):
 +
    """Tests for Firefox"""
 +
 +
    @rosatest.imagebased
 +
    def test_ff(self):
 +
        """A simple test"""
 +
        self.load_images("images")
 +
 +
        self.click("ff-start")
 +
        self.wait("ff-home-button")
 +
 +
        self.click("ff-new-tab")
 +
 +
        # Wait a little for the tab to open.
 +
        time.sleep(1)
 +
 +
        self.type_string("www.rosalab.com")
 +
        self.press_key("enter")
 +
        self.wait("rosa-logo")
 +
 +
        self.press_key("alt-f4")
 +
 +
        button = self.find("ff-close-tabs", timeout=5)
 +
        if button:
 +
            self.click(button)
 +
</pre>
 +
 +
== Creating the Tests - Tutorial #2 ==
  
 
TBD
 
TBD

Revision as of 12:16, 6 June 2013

The automated testing system for the applications running under ROSA Linux allows to check different usage scenarios of these applications by emulating user actions:

  • keyboard input
  • mouse movement and button clicks

The source code: https://abf.rosalinux.ru/spectre/rosa-autotest/

The system is build upon Python unit testing framework, Python 2.7 is needed. Besides that, the system includes a modified version of Xpresser tool.

Testing GUI Applications

The tests for GUI applications may run in two different modes in our system:

  • A test runs locally, that is, on the same machine where the application under test is running. This mode is more suitable during the development and debugging of the tests. It can also be used for testing the applications on the real hardware.
  • A test operates on the applications that run on a different (usually, virtual) machine. This is convenient for automated runs of the testing system to check the new builds of the distro. Besides, it is possible to check the boot and installation of the OS in this mode, as well as other things that are difficult to check if the tests run locally.

The tests for the applications in the installed OS should usually be written so that they could run in both modes. If the test uses only Python unittest API and the API provided by our system, this will be achieved automatically.

Testing Applications Running on a Separate Machine

Note. For most developers, it will likely be more convenient to run the tests locally instead (see below).

Setup instructions for the software needed for this mode are available here: https://abf.rosalinux.ru/spectre/rosa-autotest/tree/master/doc

The following script runs the whole test suite: launcher.py

Testing Applications Locally

Here it is assumed that everything is performed on ROSA Desktop Fresh.

  • First, install the prerequisites:
# urpmi python-distribute python-xlib opencv-devel python-opencv python-numpy python-imaging
# urpmi git scrot
# easy_install pyuserinput
  • Some image editor will also be needed to prepare the images for the tests (usually, parts of the screenshots). For example, GIMP can be used.
  • The newest version of the testing system is available in the git repository:
$ git clone https://abf.rosalinux.ru/spectre/rosa-autotest.git

Here we assume the source code of the testing system is now in /home/user/rosa-autotest.

For Python to find the needed modules when the tests are executed, one may prepare symlinks to their directories in /usr/lib/python2.7/site-packages/ (as root):

# ln -s /home/user/rosa-autotest/rosa_autotest /usr/lib/python2.7/site-packages/
# ln -s /home/user/rosa-autotest/rosatest /usr/lib/python2.7/site-packages/
# ln -s /home/user/rosa-autotest/xpresser /usr/lib/python2.7/site-packages/

The path to the modules could be specified in PYTHONPATH environment variable instead.

To check that all the necessary paths are set correctly, one may launch Python and try to import the modules, for example:

$ python -c "import rosa_autotest, rosatest, xpresser"

If this command completes successfully, the paths are OK.

Creating the Tests - Tutorial #1

Here we assume the test developer has at least basic skills in Python (to obtain these skills, it would be more than enough to study the official tutorial: http://docs.python.org/2.7/tutorial/index.html)

Let us prepare a test that launches Firefox, opens a new tab and goes to a particular web site. Everything is done here on ROSA Desktop Fresh with KDE.

First, create a file, say, test_firefox.py and images subdirectory in the same directory. images will contain the images the test will use.

Suppose the desktop looks as follows before the test starts:

Rosa autotest scr1.png

To launch Firefox, it is enough to click its icon on the panel. For the test to be able to do so automatically, let us take a screenshot and extract the icon from there: Rosa autotest ff start.png. It is often desirable to take only the inner part of that image (Rosa autotest ff start inner.png), to avoid depending on the background of the icon.

Save that image as ff-start.png in images subdirectory.

Now open test_firefox.py in a text editor of your choice and write the following there:

import time
import rosatest

class TestFirefox(rosatest.TestCaseInstalled):
    """Tests for Firefox"""

    @rosatest.imagebased
    def test_ff(self):
        """A simple test"""
        self.load_images("images")

        self.click("ff-start")

The needed modules are imported at the beginning, then TestFirefox is defined as a class derived from rosatest.TestCaseInstalled.

The derived classes of rosatest.TestCaseInstalled are used to test software on the installed system, the ones derived from rosatest.TestCaseLive - on the system booted in Live mode.

As usual in the development of the tests based on Python unittest, we should define one or more functions (test methods) in the class, each performing a test as a part of the test case. In this tutorial, it is test_ff().

The test functions that rely on image recognition and emulation of the user actions should be marked with @rosatest.imagebased decorator.

self.load_images("images") loads the images to be used by the test from the specified directory (images that has been created earlier).

self.click("ff-start") looks for the image images/ff-start.png on the screen and clicks the left mouse button at the center of the found image. If it does not find the image, an exception will be raised and the test will fail.

Now we can execute the test and check that it actually launches Firefox (assuming Firefox is not running yet). The easiest way is perhaps to use local.py script. The results of the test will be placed to results subdirectory of the current directory in this case.

python /home/user/rosa-autotest/rosatest/local.py <path to test_firefox.py>

Firefox should start as a result:

Rosa autotest scr2.png

The following will be printed to the console:

== Starting test test_firefox.TestFirefox.test_ff ==

Ran 1 test
OK

Note. Each test has an ID in the form file_name.class_name.function_name. When executed with local.py, the test will save its results to results/file_name/class_name/function_name/ subdirectory of the current directory. Subdirectory tmp/file_name/class_name/function_name/ is used to store the temporary files during the execution of the test.

At the moment, the test only launches Firefox but does not check the browser has started successfully. We can try, for example, to check that "Home" button (Rosa autotest ff home button.png) has appeared.

Take a shot of the appropriate part of the screen and save the image of that button as images/ff-home-button.png. Then add the following to test_ff():

        self.wait("ff-home-button")

wait() waits till the given image appears on the screen. If the image does show up, the execution of the test continues. If the given amount of time (30 seconds by default) has passed but the image has not appeared, wait() raises an exception, the test ends and is marked as failed.

The maximum amount of time to wait for an image can be set in timeout argument, the interval between consecutive checks - in interval, for example:

        self.wait("ff-home-button", timeout=120, interval=5)

timeout is 0, wait() will not wait at all but will rather check if the image is present immediately.

If the image has not appeared so far, the test will end with an error:

== Starting test test_firefox.TestFirefox.test_ff ==

==============================
FAIL: test_ff (test_firefox.TestFirefox)
A simple test
------------------------------
Traceback (most recent call last):
  File "/usr/lib/python2.7/site-packages/rosatest/test.py", line 349, in internal
    func(self, *args, **kwargs)
  File "/home/eugene/work/tutorial/test_firefox.py", line 13, in test_ff
    self.wait("ff-home-button", timeout=1)
  File "/usr/lib/python2.7/site-packages/rosatest/test.py", line 299, in wait
    match, "Failed to find an image on the screen: " + image)
AssertionError: Failed to find an image on the screen: ff-home-button

Ran 1 test
FAILED (failures=1)

When a test ends with an error, a screenshot will be taken at the time of failure and saved to the results directory of the test as 'scr_failure.png'. This may help analyze what has gone wrong. The log of the test (test.log in the results directory) may also contain information useful when analyzing the results, including the messages output by that test.

Note. The system uses the standard assertion methods from Python unittest. TestCase classes from rosatest module are derived from unittest.TestCase, so the tests may use the facilities from unittest directly.

Now let the test open a new tab by clicking at Rosa autotest ff new tab.png (save this image as images/ff-new-tab.png), and enter "www.rosalab.com" there. To achieve this, add the following to test_ff():

        self.click("ff-new-tab")

        # Wait a little for the tab to open.
        time.sleep(1)

        self.type_string("www.rosalab.com")

What remains is to press "Enter" and wait until Firefox opens the needed site:

Rosa autotest rosa logo.png

Code:

        self.press_key("enter")
        self.wait("rosa-logo")

"www.rosalab.com" should open as a result:

Rosa autotest scr3.png

The test may close Firefox now. To do this, let it press "alt-f4":

        self.press_key("alt-f4")

Note. press_key() can be used to emulate pressing of both single keys ("esc", "f4", etc.) and the combinations of keys. The combinations are written with a hyphen as a separator: "ctrl-alt-v", "shift-f7", "ctrl-a" and so on. The names of the keys used by the testing system are listed here: http://en.wikibooks.org/wiki/QEMU/Monitor#sendkey_keys. In addition to that list, there are some handy aliases: "ret" and "enter" can be used for the "Enter" key, "del" is an alias for "delete", "ins" - for "insert".

One should take into account that Firefox may (or may not) issue a warning about closing multiple tabs:

Rosa autotest scr4.png

The test may check, for example, whether "Close tabs" button (Rosa autotest ff close tabs.png, images/ff-close-tabs.png) appears on the screen and if it does, click it:

        button = self.find("ff-close-tabs", timeout=5)
        if button:
            self.click(button)

find() is similar to wait() with a single significant difference. If the image is not found on the screen, find() returns None but does not raise exceptions. The test is not considered failed.

If the image is found, both find() and wait() return an object that can be passed to click(), right_click(), double_click() and the like instead of the name of an image which is demonstrated above.

So, here is the complete test:

import time
import rosatest

class TestFirefox(rosatest.TestCaseInstalled):
    """Tests for Firefox"""

    @rosatest.imagebased
    def test_ff(self):
        """A simple test"""
        self.load_images("images")

        self.click("ff-start")
        self.wait("ff-home-button")

        self.click("ff-new-tab")

        # Wait a little for the tab to open.
        time.sleep(1)

        self.type_string("www.rosalab.com")
        self.press_key("enter")
        self.wait("rosa-logo")

        self.press_key("alt-f4")

        button = self.find("ff-close-tabs", timeout=5)
        if button:
            self.click(button)

Creating the Tests - Tutorial #2

TBD