Extremely opinionated,
use on your own risk,
don't trust anyone
talk by that white dude with beautiful voice
Grant me the serenity to omit tests for things that arenβt worth testing,
Courage to write tests for things that deserve test coverage,
And wisdom to know the difference.
Β
Everyone tell testing is great, I just don't know how to do it
Testing is daunting π«
What is a good test?
Where do I start?
TDD?? BDD?? Acceptance??
Exploration??
How would I test my test?
To have a good sleep at night
Test everything, Yo
Recent
Expertise
Core
Problematic
Risk
Parts of Application
Recent
Expertise
Core
Problematic
Risk
I'm in Trouble BINGO
Bob developed new core functionality, sadly he got sick and the code is so complex nobody can read it
Functionality
Pro-tip: Machines are better at generating unexpected values, see Hypothesis package
These kind of tests run against real-life application, with all it dependencies, emulating user flows.
Also can be called integration tests, they test how different parts of the application work with each other in the context of the feature
Fastest type of test as they should not do any database requests or writing to the disc, they test one function, one class in isolation.
Content Warning: Subjective opinion
Service Tests
Integration Tests
Functional Tests
You are free to use whatever, but it is not a user journey yet
Anton's Reasoning
Everything will be reversed for E2E tests, lol
methodology/approach
Everything we need to setup to successfully reproduce a test in isolation
The part of the code under test
This is the part where you confirm your expectations
import pytest
@pytest.mark.django_db
def test_get_dataframe_for_buy_act_with_reports(
buy_act, company, report_with_report_file
):
# GIVEN Act of buying attached to a Report with report file
# and report_file contains 3 rows, 2 of them with unrelated
# acts_rows and one with attached buy_acts
buy_act.seller = company
buy_act.save()
report_with_report_file.act_of_purchase.add(buy_act)
# WHEN we creating a dataframe for XLSX export
df = buy_act.get_dataframe()
# THEN it should contain only related to act report rows
assert df.shape[0] == 1
for row in df.itertuples(index=False, name="Row"):
assert row.buyer == company.nameEspecially crucial when adding tests for existing functionality
class VerificationAttemptResultView(GenericAPIView):
"""Returns Verification results of the request as they sent to the Monolith"""
authentication_classes = [authentication.TokenAuthentication]
permission_classes = [permissions.IsAuthenticated]
serializer_class = VerificationAttemptResultSerializer
def post(self, request: Request) -> Response:
verifier = self.get_serializer(data=request.data)
if not verifier.is_valid():
return Response(status=400)
data = verifier.data
if data["type"] == VerificationType.TYPE_PROOF_OF_ADDRESS:
return Response(status=400)
try:
verification_request = VerificationRequest.objects.get(
user_id=data["user_id"],
verification_reference=data["verification_reference"],
)
except VerificationRequest.DoesNotExist:
return Response(status=404)
...
if no_history_created or attempt_is_missing:
return Response(status=500)
...
if last_verification_history_record.status not in FINAL_STATES:
return Response(status=400)
return Response(json.loads(last_verification_attempt.verification_results))
def test_attempt_response_view_permissions(api_client):
# GIVEN unauthorized client
# WHEN it does request to fetch results
res = api_client.post(reverse("verification_attempt_result"), data={"foo": "bar"})
# THEN permissions should be checked and correct code returned
assert res.status_code == 4011. Permissions check
2. Validation Checks
@pytest.mark.django_db
def test_attempt_response_view_invalid_data(authenticated_api_client):
# GIVEN authenticated client and invalid data
invalid_data = {"user_id": 123, "foo": "bar"}
# WHEN client makes request to results endpoint
res = authenticated_api_client.post(
reverse("verification_attempt_result"),
data={"user_id": 123, "foo": "bar"},
)
# THEN validation error is returned with correct error code
assert res.status_code == 400
response_body = res.json()
assert response_body["error"]["message"] == "Validation Error"
@pytest.mark.django_db
def test_attempt_response_view_proof_of_address_not_implemented(authenticated_api_client):
# GIVEN request data with not implemented yet functionality
data = {
"user_id": "test",
"verification_reference": "ZZZ",
"type": VerificationType.TYPE_PROOF_OF_ADDRESS,
}
# WHEN making request to result endpoint
res = authenticated_api_client.post(
reverse("verification_attempt_result"),
data=data,
)
# THEN Not implemented message should be part of response
assert res.status_code == 400
response_body = res.json()
assert "not implemented" in response_body["error"]["message"]3. Logic Branches
@pytest.mark.django_db
def test_attempt_response_view_missing_request(authenticated_api_client):
# GIVEN valid request body AND no verification request saved in DB
verification_attempt_request_data = {
"user_id": "test",
"verification_reference": "ZZZ",
}
# WHEN request made to result endpoint
res = authenticated_api_client.post(
reverse("verification_attempt_result"),
data=verification_attempt_request_data,
)
# THEN Not found code should be returned without body
assert res.status_code == 404
assert res.content == b""
@pytest.mark.django_db
def test_attempt_response_view_missing_attempt(authenticated_api_client):
# GIVEN valid request body AND verification request without attempt
verification_attempt_request_data = {
"user_id": "test",
"verification_reference": "ZZZ",
}
VerificationRequestF.create(
user_id=verification_attempt_request_data["user_id"],
verification_reference=verification_attempt_request_data[
"verification_reference"
],
)
# WHEN request made to result endpoint
res = authenticated_api_client.post(
reverse("verification_attempt_result"),
data=verification_attempt_request_data,
)
# THEN 500 should be returned as no request should exist without attempt
assert res.status_code == 500
assert res.content == b""
@pytest.mark.django_db
def test_attempt_response_view_attempt_is_pending(authenticated_api_client):
# GIVEN valid request body
# AND verification request with attempt
# AND Attempt status is WAITING
verification_attempt_request_data = {
"user_id": "test",
"verification_reference": "ZZZ",
}
VerificationAttemptF.create(
status=VerificationAttemptStatus.WAITING,
verification_request__user_id="test",
verification_request__verification_reference="ZZZ",
)
# WHEN request made to result endpoint
res = authenticated_api_client.post(
reverse("verification_attempt_result"),
data=verification_attempt_request_data,
)
# THEN 400 should be returned with the specific "PENDING" reason
assert res.status_code == 400
response_body = res.json()
assert response_body["error"]["message"] == "Verification attempt is pending"
4. Happy Path
@pytest.mark.django_db
def test_attempt_response_view_happy_path(authenticated_api_client):
# GIVEN valid request body
# AND verification request with attempt
# AND Attempt status is COMPLETED
verification_attempt_request_data = {
"user_id": "test",
"verification_reference": "ZZZ",
}
verification_results = {"status": VerificationStatus.APPROVED.value}
VerificationAttemptF.create(
status=VerificationAttemptStatus.COMPLETED,
verification_results=json.dumps(verification_results),
verification_request__user_id=verification_attempt_request_data["user_id"],
verification_request__verification_reference=verification_attempt_request_data[
"verification_reference"
],
)
# WHEN request made to result endpoint
res = authenticated_api_client.post(
reverse("verification_attempt_result"),
data=verification_attempt_request_data,
)
# THEN request is successful and we return results
assert res.status_code == 200
assert res.json() == verification_results
How to start with testing?
Such empty
Resources
Podcasts
Articles