Skip to content

friedolinfoerder/python-syseng

Repository files navigation

System Engineering: Python

Hochschule der Medien Stuttgart
Studiengang Computer Science and Media
WS 2013/2014

Team:

Professor:
Prof. Dr. Walter Kriha

Link to the reference project: https://github.com/schreon/morepeople-server

Table of Contents

Unit Tests

Unit tests can be easily written using the unittest module of the Python standard library together with the Flask Test Client. Normally, the database / persistence layer should be mocked and only the interfaces of the components under test should be accessed from within the tests. But Flask server applications are very easy to test by directly accessing its routes using the test client while running the application in debug mode.

As a reference, have a look at our morepeople server which is based on these technologies.

In the following, we show how we incorporate a real mongodb instance in our morepeople server tests, making them actually integration tests:

class FlaskAppTestCase(unittest.TestCase):

    @classmethod
    def setUpClass(self):
        os.environ['MORE_PEOPLE_DB'] = 'localhost'
        os.environ['MORE_PEOPLE_DB_NAME'] = 'morepeople_testing'
        os.environ['MORE_PEOPLE_LOG'] = 'morepeople_test.log'
        import server        
        self.server = server
        # chooses testing config, i.e. in-memory db:
        self.app = self.server.app.test_client()
        
    def setUp(self):
        self.server.users.remove({})
        self.server.queue.remove({})
        self.server.tags.remove({})
        self.server.lobbies.remove({})
        self.server.matches.remove({})
        self.server.evaluations.remove({})

    def test_server_being_up(self):
        """ Test if self.server.is up. """
        response = self.app.get('/')
        self.assertEqual(response.status_code, 200)

In the code above, we first set up the python module in the setUpClass method. Before the server module is imported, environment variables are set, so the application will access a defined mongodb instance. This is important when running the tests in an continuous integration environment: in no case should the tests access the production database.

The setUp method is called before each test case and it clears all entries out of the mongodb collections.

The last method named test_server_being_up is the first very simple test case. It uses the test client to simulate a HTTP request to the root route / and checks if the response's status code is set to 200.

The following test is a bit more sophisticated:

    def test_enqueue_user(self):
        """ test_enqueue_user """
        data = {
            'USER_ID' : 'test_1234567',
            'USER_NAME' : 'test__user',
            'MATCH_TAG' : "beer",
            'LOC': {'lng' : 40, 'lat' : 9 }
            }

        headers = [('Content-Type', 'application/json')]


        # should not be there initially
        self.assertTrue(self.server.queue.find_one(data) is None)

        response = self.app.post('/queue', headers, data=json.dumps(data))

        # should be there now
        self.assertTrue(self.server.queue.find_one({'USER_ID' : data['USER_ID']}) is not None)

What happens here is that a request containing json data is sent to the /queue route. Instead of inspecting the response, we directly access the server application and assert that a new data entry has been created in the mongodb instance. This kind of test is very powerful, because we only need to access the interface of the Flask application (basically the REST routes) and can assert the correct functionality of the application by directly accessing the persistence layer.

Automated Tests

The tests can be run by using the nose module. Nose will scan all subdirectories of the current working directory for python files. Classes which inherit from the unittest.TestCase are then again scanned for methods beginning with "test" - for example test_enqueue_user. Nose then runs these tests and collects the results.

Nose can be called by simply typing nosetests. The test results can be saved in the XUnit format by adding the command line argument --with-xunit.

Coverage

Nose ships with the coverage package and incorporates it additional command line arguments:

nosetests --with-xunit --with-coverage --cover-package=server --cover-html --cover-html-dir=htmlcov tests

The line above runs all tests within the tests folder and generates Xunit results. Additionally, the coverage plugin is run by specifying the command --with-coverage. By also specifiying the package name which should be tests (in our case server ), 3rd party libraries are not included in the test coverage analysis. The last two arguments --cover-html and --cover-html-dir=htmlcov simply state that the coverage results should be created as HTML, into a directory called htmlcov.

We created some bash scripts which are automatically run on our development server whenever we push any code changes to our git repository. The resulting code coverage reports can be viewed following this link.

Static Code Analysis

Static code analysis can be done using the pylint library. It scans the source files without actually running them and generates reports indicating several metrics. The following command generates an HTML report for the server module and saves them in a file named index.html:

pylint --output-format=html server >> index.html

As with the automated unit tests and coverage, we generate the pylint reports automatically on every git push. The resulting reports belonging to our morepeople project can be viewed here: http://109.230.231.200/werkbank/lint/

SonarQube

All the techniques mentioned above are capable of generating reports in all usual data exchange formats (as well as HTML which can be viewed in the browser). Tools like SonarQube aggregate such results and display them in a dashboard, providing an all-in-one overview. For example, this view nicely displays the results of pylint which were generated based on our morepeople server: http://109.230.231.200:9000/drilldown/issues/morepeople.server?severity=MAJOR

To automatically collect the test, coverage and linter results and put them into the SonarQube database, the so called sonar-runner, is necessary. For each programming language, a separate plugin is needed. In this case, we use the Python Plugin.

Integration into Git

The execution of automated unit tests, static code analysis and the transfer of the results to the SonarQube database can scheduled after every change to the code base. We developed a so called post-receive hook which starts another linux shellscript running in the background. The scripts are run whenever a developer pushes code changes to our git respository. The following script must be named post-receive and must be placed into the hooks folder of the git repository on the server:

#!/bin/sh
echo "Testing and deploying app."
exec /path/to/repository/hooks/updaterepo.sh >&- 2>&- &

As a convention, the git process running on the server will execute this script after code changes have been applied to the repository. The script starts another script named updaterepo.sh so that it runs in a separate process in the background. Here are the contents of updaterepo.sh:

#!/bin/sh
rm -rf /path/to/test-folder/
git clone /path/to/repository /path/to/test-folder/application-name
cd /path/to/test-folder/application-name
/path/to/virtualenv/bin/coverage erase
/path/to/virtualenv/bin/nosetests -v --with-xunit --with-coverage --cover-erase --cover-package=server tests --cover-branches --cover-html --cover-html-dir=htmlcov --cover-xml --cover-xml-file=coverage.xml
rm -rf /path/to/reports/htmlcov
cp -R /path/to/test-folder/application-name/htmlcov /path/to/reports/htmlcov
cd /path/to/test-folder/application-name
/path/to/virtualenv/pylint --output-format=html server >> /path/to/reports/lint/index.html

Profiling

We have two profiling strategies:

  • creating profiling snapshots at every push to the repository
  • in detail profiling with tools like the Flask Debug-toolbar

###Creating profiling snapshots

At every push to the repository the tests will be started in debug mode with profiling enabled. So for every request there is the following profiling information:

So the changes and the corresponding effects to the code can easily tracked by looking at performance profile files. You can go the server and compare two versions in the repository. The folder structure is like this:

profiling
    2014-02-17_18:20:37
        GET.root.000738ms
            callstack.txt
            dot.png
            profile.kgrind
            profile.prof
        GET.user.002123ms
            callstack.txt
            dot.png
            profile.kgrind
            profile.prof
    2014-02-19_11:14:20
        GET.root.001099ms
            callstack.txt
            dot.png
            profile.kgrind
            profile.prof
        GET.user.003041ms
            callstack.txt
            dot.png
            profile.kgrind
            profile.prof

The script which processes the profiling and creates the files is the profile.py script.

An example of a created dot.png file:

And the corresponding (relevant) content of the callstack file:

Wed Feb 19 11:14:21 2014    profiling/2014-02-19_11:14:20/GET.root.001099ms/profile.prof

         100330 function calls in 1.099 seconds

   Ordered by: internal time, function name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
   100000    1.083    0.000    1.083    0.000 /home/friedolin/git/python-profiling/app.py:12(calculate)
        1    0.015    0.015    1.099    1.099 /home/friedolin/git/python-profiling/app.py:17(hello_world)

Here in this example you can see that the calculate-function takes almost all of the execution time.

###In detail profiling

Creating profiling snapshots is a good thing to see performance changes in your codebase, but this is not enough to fix serious performance problems. Therefore you should profile your code manually. We've take a look at the following tools for python:

####Live profiling with the Flask Debug-toolbar

For the flask framework there is a very useful tool called Debug-toolbar. With this you can not only inspect the request variables, templates, configuration variables, logging but also the callstack of the creation of the visited page and a line profiler (LineProfilerPanel-Plugin) which highlights critical lines, as you can see in this minimal example:

This makes it very easy and comfortable to discover the internals of the application and find possible bugs and performance bottlenecks.

How you can use a line profiler for profiling memory usage is described in the chapter "Memory profiling".

####Using kcachegrind to visualize the calltree

To get a better understanding what's going on within the application and to understand the flow and the performance impact of functions of your application, we used kcachegrind to visualize the call graph and the call map. To use kcachegrind you have to install it

sudo apt-get install kcachegrind

and convert the profile data to the right output format. We are doing this in the discribed automated profile creation process. The script pyprof2calltree.py provides all necessary tools to make the conversion. This is basically how it works:

from pyprof2calltree import convert
convert('profile.prof', 'profile.kgrind')

With this file and kcachegrind you can visualize the profiling as the following screenshot illustrates:

kcachegrind is a great tool to actually use the profile data to improve the performance of the application. With it's various options and different visualizations you faster see the problems in your code.

####Memory profiling

You must install the python-dev package, the psutils module and the memory profiler module:

sudo apt-get install python-dev
sudo pip install psutil
sudo pip install memory_profiler

Then you can annotate the function you want to inspect:

@profile
@app.route('/memory')
def xrange_vs_range():
    xr = xrange(2**16)
	r  = range(2**16)

	return str(len(r))

And with the additional script

from app import app

@profile
def profiling():
    app.test_client().get('/memory')

profiling()

and the additional setting -m memory_profiler when starting the python interpreter

python -m memory_profiler profile_memory.py

we can trigger the memory profiling and get this result:

Filename: app.py

Line #    Mem usage    Increment   Line Contents
================================================
    24   15.523 MiB    0.000 MiB   @profile
    25                             @app.route('/memory')
    26                             def xrange_vs_range():
    27   15.527 MiB    0.004 MiB       xr = xrange(2**16)
    28   17.562 MiB    2.035 MiB   	r  = range(2**16)
    29                             
    30   17.562 MiB    0.000 MiB   	return len(r)


Filename: memory_profile.py

Line #    Mem usage    Increment   Line Contents
================================================
     3   15.156 MiB    0.000 MiB   @profile
     4                             def profiling():
     5                             	app.test_client().get('/memory')

This tool is perfect to find memory leaks in code handeling your requests. With the use of app.test_client() it works seamlessly with the flask framework.

Excursion: Test-Driven Development of Android Applications

Have a look at the pdf CI_with_Android.pdf to get more information about this CI setup for Android:

There is more information about this topic at this location: https://github.com/friedolinfoerder/android-test

About

Information about development tools for python

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages