-
Notifications
You must be signed in to change notification settings - Fork 10
Testing Django, Flask applications
In hs-test-python
testing library there is a possibility to test backend applications based on Flask
and Django
libraries. The testing process somewhat similar, so this page covers both frameworks.
First of all, you need to extend an appropriate class. In case of Django
, you need to extend DjangoTest
, and in case of Flask
you need to extend FlaskTest
.
Import an appropriate class from the hstest
module:
from hstest import DjangoTest
from hstest import FlaskTest
And then extend an appropriate class:
class ServerTest(FlaskTest):
...
class ServerTest(DjangoTest):
...
Inside the body of the class, you can define source
- the path to file you want to execute.
With Django
it's easy, the hs-test-python
library will automatically find manage.py
across all the user's files and run this file with arguments runserver PORT --noreload
. So you don't need to set source
.
With Flask
the file to execute can have any generic name, so you are forced to set the source
field.
The source
field can be of type str
or tuple
or list
. You should set the module name, not the path to the file. For example, if the file name is server.py
then the module would be server
. Another case is when the file path is module1/client.py
then the module name would be module1.client
.
class ServerTest(FlaskTest):
source = 'module1.client'
...
In this case, a free port will be found automatically in the range 8000-8099 inclusive. But you can define the port specifically as a second item of the tuple. Notice that if you don't write parentheses the Python would understand that as a tuple.
Examples:
source = 'module1.client', 5001
source = ('module1.client', 5001)
When extending FlaskTest
you can also set multiple sources, meaning multiple Flask
applications will be available when testing. You can set all the sources in the list. Every item on the list should be either str
or tuple
. DjangoTest
supports a single execution source at the moment.
Examples:
source = ['module1.client']
source = ['module1.client', 'server']
source = [('module1.client', 5001)]
source = [('module1.client', 5001), ('server', 5002)]
source = ['module1.client', ('server', 5002)]
source = [('module1.client', 5001), 'server']
Note: in Django
and Flask
host and port are sent to the user's program via command-line arguments. Django
manages them automatically in manage.py
autogenerated file but Flask
doesn't. The host and port are sent to the Flask
application via the 2nd command-line argument in the format host:port
, for example localhost:8000
. You need the following code to address that:
if __name__ == '__main__':
if len(sys.argv) > 1:
arg_host, arg_port = sys.argv[1].split(':')
app.run(host=arg_host, port=arg_port)
else:
app.run()
The sample above can be run with or without command line arguments being passed. This way, users don't need to worry about host and port while testing their program by themselves. Please, include this sample in the initial template for the user in Hyperskill Flask projects.
When extending DjangoTest
you can use a separate database for testing. For this, you need to set use_database = True
.
class ServerTest(DjangoTest):
use_database = True
...
The hs-test-python
library
- Sets the environment variable
HYPERSKILL_TEST_DATABASE
todb.test.sqlite3
- Creates a file
db.test.sqlite3
. If it already exists then the library deletes it and creates an empty file. - Runs
python manage.py migrate
command to set the database with the correct tables.
If you want to use another name of the database, you should set test_database
field with the name of the database.
class ServerTest(DjangoTest):
use_database = True
test_database = 'another_name.db'
...
In the initial template in file settings.py
you should use the following code:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.environ.get('HYPERSKILL_TEST_DATABASE') or os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
You should use @dynamic_test
decorator to write tests. See this article on how to write dynamic tests.
Use self.get(address)
to make GET
request to the source. If you have multiple sources, you should specify a source also, like in source
field.
Example:
class ServerTest(DjangoTest):
@dynamic_test
def test1(self):
hw = self.get('/')
if hw != 'Hello, World!':
return wrong(f'Django service should return "Hello, World!" '
f'performing "GET /", found "{hw}"')
return correct()
Other example:
class ServerTest(FlaskTest):
source = ['server', 'module1.client']
@dynamic_test
def test1(self):
hw = self.get('/', source='module1.client')
if hw != 'Hello, World Client!':
return wrong(f'Flask client should return "Hello, World Client!" '
f'performing "GET /", found "{hw}"')
return correct()
If you want to perform POST
request, or you want to use requests
library, you can use self.get_url(address)
to get host and port and attached address as a string. By default, the attached address is an empty string.
Example:
import requests
class ServerTest(DjangoTest):
@dynamic_test
def test1(self):
hw = requests.get(self.get_url('/home'))
if hw != 'Home page':
return wrong(f'Django service should return "Home page" '
f'performing "GET /home", found "{hw}"')
return correct()
Another example:
import requests
class ServerTest(FlaskTest):
source = ['server', 'module1.client']
@dynamic_test
def test1(self):
hw = requests.get(self.get_url('/home', source='server'))
if hw != 'Home Server':
return wrong(f'Flask server should return "Home Server" '
f'performing "GET /home", found "{hw}"')
return correct()
Sometimes you want to use the same file for every stage of the project containing all the tests for all the stages. You can use a separate class for that, but that class should be located in the test
folder. To import it correctly, you should also create an empty __init__.py
in the test
folder. So, the file hierarchy should look like the following:
stage1
manage.py
...
test
__init__.py
base.py
tests.py
stage2
test
__init__.py
base.py
tests.py
...
base.py
might look like the following. Notice that before each method there is no @dynamic_test
decorator.
from hstest import DjangoTest
class BaseServerTest(DjangoTest):
use_database = True
def test1(self):
...
def test2(self):
...
def test3(self):
...
...
And you can use data
parameter to quickly chose tests you need for the specified stage. Here's tests.py
example:
from test.base import BaseServerTest
class Stage1Test(BaseServerTest):
funcs = [
BaseServerTest.test1,
BaseServerTest.test3,
BaseServerTest.test5,
...
]
@dynamic_test(data=funcs)
def test(self, func):
return func(self)
if __name__ == '__main__':
Stage1Test().run_tests()