Let’s walk through a bunch of use cases that i’ve learnt over a period of time for writing tests.
Introduction:
As a developer when I started writing code I didn’t realized the importance of a test case. The first time I heard test driven development, I wanted to see what that is and I found out that test driven development is a good way of writing code rather than just writing code and expect it to work. Let’s walk through a bunch of use cases that i’ve learnt over a period of time.
Initial setup
mkdir testing && cd testing && poetry init
Note: Istructions on poetry setup is easy. Find out more in my article
“Setup Django with poetry”
Install packages:
$ poetry add <package-name>
Shenanigans of datetime
Lets write a quick function that returns a datetime object as below to functions.py file
import datetime
def five_days_from_now():
return datetime.datetime.now() + datetime.timedelta(days=5)
A simple test for this would be
import datetime
from functions import five_days_from_now
def test_five_days_from_now():
assert five_days_from_now() == datetime.datetime.now() + datetime.timedelta(days=5)
Lets run tests
$ poetry run pytest
============================= test session starts ==============================
platform darwin -- Python 3.6.8rc1, pytest-5.4.2, py-1.8.1, pluggy-0.13.1
collected 1 item
test_functions.py F [100%]
=================================== FAILURES ===================================
___________________________ test_five_days_from_now ____________________________
def test_five_days_from_now():
> assert five_days_from_now() == datetime.datetime.now() + datetime.timedelta(days=5)
E AssertionError: assert datetime.datetime(2020, 5, 15, 16, 26, 42, 528109) == datetime.datetime(2020, 5, 20, 16, 26, 42, 528114)
E + where datetime.datetime(2020, 5, 15, 16, 26, 42, 528109) = five_days_from_now()
E + and datetime.datetime(2020, 5, 20, 16, 26, 42, 528114) = <built-in method now of type object at 0x104d4d680>()
E + where <built-in method now of type object at 0x104d4d680> = <class 'datetime.datetime'>.now
E + where <class 'datetime.datetime'> = datetime.datetime
test_functions.py:7: AssertionError
=========================== short test summary info ============================
FAILED test_functions.py::test_five_days_from_now - AssertionError: assert da...
============================== 1 failed in 0.05s ===============================
Well obviously that was gonna fail because there is a difference in the time when the test calling datetime.now() and function calling datetime.now(). You might see that this is the case with some of your tests and the first thing that might come to your mind is that mock
datetime. Let’s do this in a better way!!!
import datetime
import freezegun
from functions import five_days_from_now
@freezegun.freeze_time("2020-05-15")
def test_five_days_from_now():
assert five_days_from_now() == datetime.datetime.now() + datetime.timedelta(days=5)
Let’s run the test again
$ poetry run pytest
============================== test session starts =============================
platform darwin -- Python 3.6.8rc1, pytest-5.4.2, py-1.8.1, pluggy-0.13.1
collected 1 item
test_functions.py . [100%]
=============================== 1 passed in 0.29s ==============================
$
Hooray!! As long as the freezegun is used as decorator the datetime will always give 2020-05-15
as the date.
It is possible that sometimes we might not wana mock everytime. We can use with context to make this happen.
Let’s edit our test case again
import datetime
import freezegun
from functions import five_days_from_now
def test_five_days_from_now():
# Freezing time
with freezegun.freeze_time("2012-01-14"):
assert five_days_from_now() == datetime.datetime.now() + datetime.timedelta(days=5)
# call five_days_from_now without freeze
assert five_days_from_now() != datetime.datetime.now() + datetime.timedelta(days=5)
Let’s run our test again
$ poetry run pytest
============================== test session starts =============================
platform darwin -- Python 3.6.8rc1, pytest-5.4.2, py-1.8.1, pluggy-0.13.1
collected 1 item
test_functions.py . [100%]
=============================== 1 passed in 0.11s ==============================
$
Freezegun will only mock the datetime with in the context and its lifted once it comes out of the context.
Enough of the Shenanigans lets talk moto.
Test with moto
Lets add a function that creates a s3 file in functions.py
import boto3
def create_file(path, content):
"""
Creates a file in s3 bucket given a path to the file and content to write
:path: <str>
:content: <str>
"""
client = boto3.client("s3", region_name="us-east-1")
client.put_object(
Body=content.encode("utf-8"), Bucket="bucket.proxyroot.com", Key=path
)
Let’s create a fixture with pytest for s3 in test_functions.py
import boto3
from moto import mock_s3
@pytest.fixture(scope="function")
def s3_client():
with mock_s3():
conn = boto3.resource("s3", region_name="us-east-1")
# create a bucket
conn.create_bucket(Bucket="bucket.proxyroot.com")
client = boto3.client("s3", region_name="us-east-1")
yield client
Let’s create a test in test_functions.py
from functions import create_file
def test_create_file(s3_client):
# Create test.txt file
create_file("tests/test.txt", "hello world")
# get the object contents
object = s3_client.get_object(Bucket="bucket.proxyroot.com", Key="tests/test.txt")
data = object["Body"].read().decode("utf-8")
assert data == "hello world"
Lets run the test
$ poetry run pytest test_functions.py::test_create_file
=============================================== test session starts ================================================
platform darwin -- Python 3.6.8rc1, pytest-5.4.2, py-1.8.1, pluggy-0.13.1
collected 1 item
test_functions.py . [100%]
=========================================== 1 passed, 1 warning in 3.36s ===========================================
Reusing fixtures
If you notice that we have been adding the fixtures and tests in the same file and what if we need to re-use the fixtures across the entire project?
Create a file conftest.py in your folder testing/ and add the fixtures in it. That will make the fixture available for all your tests in that folder.
Try running the tests again and it will pass.
The code for the above experiments can be found at pytest-tests
There are currently no comments on this article, be the first to add one below