Created on 25 Jan 2021 ; Modified on 28 Jan 2021
This is the sixth part of an article about Flask, as follows:
This is the sixth part of a Flask project to show a single page in two different versions:
Here we show how add unit tests to check our application.
First, it's useful a little change in single_page/__init__.py. Then we are going to see how add unit tests.
It's useful to modify the factory function create_app in single_page/__init__.py, otherwise during tests we have log mechanisms activated. This would be a little redondant. We would get an output in console as the follow:
(venv) >python -m unittest test_single_page.py [2021-01-25 18:52:44,036] DEBUG in __init__: Covid application starts . ---------------------------------------------------------------------- Ran 1 test in 0.064s
where the second line is a debugging log. Usually not very useful during a test session.
To avoid this we add a parameter to function create_app to set True during a test session. single_page/__init__.py become:
1 ... <CUT> ... 2 3 def create_app(test=False): # +- 4 '''create and configure the app''' 5 app = Flask(__name__, instance_relative_config=True) 6 7 # ensure the instance folder exists 8 if not os.path.exists(app.instance_path): 9 os.makedirs(app.instance_path) 10 11 set_config(app) 12 if not test: # + 13 set_logger(app) # +- 14 15 from .oneel import views as views1 16 app.register_blueprint(views1.oneel) 17 18 from .twoels import views as views2 19 app.register_blueprint(views2.twoels) 20 21 babel.init_app(app) 22 sitemap.init_app(app) 23 24 @app.route('/sitemap') 25 def ep_sitemap(): # endpoint for sitemap 26 return sitemap.sitemap(), 200, {'Content-Type': 'text/xml', } 27 28 return app 29 30 ... <CUT> ...
So we set logger only if parameter test is False.
Now the hard part. We go in flask_single_page/tests and we add the file tests/test_single_page.py:
1 # import std libs 2 import os 3 import sys 4 import unittest 5 6 # import project's libs 7 # we need to add the project directory to pythonpath to find project's module(s) in development PC without installing it 8 basedir, _ = os.path.split(os.path.abspath(os.path.dirname(__file__)).replace('\\', '/')) # basedir is the project's dir, one level up of our application 9 sys.path.insert(1, basedir) # inserting in pythonpath @ ndx==1 because 0 is reserved for local directory 10 from single_page import create_app 11 12 13 class InitTest(unittest.TestCase): 14 '''testing URLs of views''' 15 16 def setUp(self): 17 self.app = create_app(test=True) 18 19 def tearDown(self): 20 pass 21 22 def test_sitemap(self): 23 with self.app.test_client() as client: 24 response = client.get('/sitemap') 25 xml = response.data.decode('utf8') # type(html) == type(str) 26 self.assertEqual(response.content_type, 'text/xml') 27 self.assertTrue(xml.startswith('<?xml ')) 28 self.assertIn('<loc>http://localhost/en/2l/index.html</loc>', xml) 29 self.assertTrue(xml.endswith('</urlset>')) 30 31 if __name__ == '__main__': 32 unittest.main()
We need (line 4) the python's library unittest.
Then we need to import the application to test. In Python we can load a library only starting from directories that are present in the environment variable pythonpath (something like environment variable path where discover executables to run by the operating system). But our Python's environment knows anything about our (not installed) application. So, in lines 8 and 9 we insert our application base directory in pythonpath, so that we can import from module single_page its function create_app.
Class InitTest, mandatory derived from unittest.TestCase, is a folder to group all tests about a section of our module under exam.
Methods setUp (line 16) and tearDown are standard methods to execute before (setUp) and after (tearDown) each test. As we can see, we use setUp to initialize the application and store it in self.app.
Other methods (line 22) are our tests. Their names must start with prefix test. In our case, we have only one method, named test_sitemap.
In body of test_sitemap we call the Flask's self.app.test_client method. We can see this one as a sort of web server. Calling it with client.get('/sitemap') (@ line 24) give us a binary response equal to the response sent from our application to a web browser calling for url https://host:port/sitemap.
Next step (line 25) we decode from binary to utf-8 data code, and finally we test our result to check:
As we tested for our module single_page, we can use unittest file(s) to test our single apps one by one.
Wait, how do we start tests? From folder tests we can fire tests about a single module calling as:
(venv) >python test_single_page.py . ---------------------------------------------------------------------- Ran 1 test in 0.059s OK
where we are firing tests about single_page.py module.
Otherwise we can call unittest library as a module. In this case unittest will discover each python file in current directory whose name starts with prefix test and will run it:
(venv) >python -m unittest . ---------------------------------------------------------------------- Ran 1 test in 0.057s OK
Please, don't stop here. There is a lot to discover digging in python's unittest environment. And about the importance of test activities.
Enjoy, ldfa