Last time we learned how to create a basic Kivy application that will be generated with Cookiecutter. The next logical steps are to add tests and docs. Additionally we will create a setup.py as well as a MAKEFILE.
The template it it’s current state contains the following directories and files:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
$ tree cookiedozer cookiedozer/ ├── cookiecutter.json ├── {{cookiecutter.repo_name}} │ ├── {{cookiecutter.repo_name}} │ │ ├── {{cookiecutter.app_class_name}}.kv │ │ ├── {{cookiecutter.repo_name}}.py │ │ ├── __init__.py │ │ └── main.py │ ├── LICENSE │ └── README.rst ├── cookiedozer01.png ├── cookiedozer02.png ├── hooks │ └── post_gen_project.py ├── LICENSE └── README.rst |
Set up a TestSuite
There a various ways to manage tests in your app, yet I like to stick to the suggestions at http://pytest.org/latest/goodpractises.html#choosing-a-test-layout-import-rules and keep the tests separated from our application code. By doing so we can easily exclude them from the android package.
1 2 |
$ cd cookiedozer $ mkdir \{\{cookiecutter.repo_name\}\}/tests |
Let’s implement a very rudimentary test:
1 2 |
$ cd \{\{cookiecutter.repo_name\}\}/tests $ touch test_{{cookiecutter.repo_name}}.py |
From my experience writing meaningful and yet effective tests for GUIs is not trivial. So I always try my best to strictly separate the business logic from the user interface and have most of the functionality covered by automated tests. In this particular case I’d like to demonstrate how to write tests for a kivy app though.
Actually it took quite a bit of time to figure out how to launch a non-blocking app while still having access to all of it’s attributes. Yet I eventually managed it by using the Interactive Launcher.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# -*- coding: utf-8 -*- import pytest @pytest.fixture(scope="module") def app(request): """Uses the InteractiveLauncher to provide access to an app instance. The finalizer stops the launcher once the tests are finished. Returns: :class:`{{cookiecutter.app_class_name}}`: App instance """ from kivy.interactive import InteractiveLauncher from {{cookiecutter.repo_name}}.{{cookiecutter.repo_name}} import {{cookiecutter.app_class_name}} launcher = InteractiveLauncher({{cookiecutter.app_class_name}}()) def stop_launcher(): launcher.safeOut() launcher.stop() request.addfinalizer(stop_launcher) launcher.run() launcher.safeIn() return launcher.app def test_app_title(app): """Simply tests if the default app title meets the expectations. Args: app (:class:`{{cookiecutter.app_class_name}}`): Default app instance Raises: AssertionError: If the title does not match """ assert app.title == '{{cookiecutter.app_title}}' @pytest.fixture(scope="module") def carousel(app): """Fixture to get the carousel widget of the test app.""" return app.carousel def test_carousel(carousel): """Test for the carousel widget of the app checking the slides' names and the text of one of the slide labels. Args: carousel (:class:`Carousel`): Carousel widget of :class:`{{cookiecutter.app_class_name}}` Raises: AssertionError: If the first slide does not contain *Hello* AssertionError: If the names of the slides do not match the expectations """ assert '{{cookiecutter.app_title}}' in carousel.current_slide.text names = [slide.name for slide in carousel.slides] expected = ['hello', 'kivy', 'cookiecutterdozer', 'license', 'github'] assert names == expected |
As you see I am using a special syntax for my doc strings with respect to the sphinx doc generation, of which more later.
Following the advice of the aforementioned py.test docs , I do not create an __init__.py inside the tests directory. To run the the suite later on we need to install our package first. Otherwise we are not able to import from any of the application modules.
On the next page of this tutorial we are going to add a setup.py to our template and learn how run the tests.