About me ● Professional Software Engineer since June 2010 ● Django user since February 2018 ● Software Engineering Manager at ● SyDjango co-organiser since August 2019 @tyomo4ka
XCore Project
XCore myTrip.com GoToGate Trip.ru XClaim XCMS XCover.com Xtranet BrightWrite Partners
Dates ● Development started in Feb 2018 ● Live since June 2019 Platform ● Python 3.6-3.7 ● Django 2.0–2.2 ● DRF 3.9-3.10 ● 49,520 LOC ● 826 tests run in ~52 sec on Bitbucket pipelines ● 86.9% code coverage XCore Facts
v1.0 early days
Django ● Extends unittest ● Django helpers: client, settings, db fixtures, etc ● Extended and Django-specific assertions
Django ● django.test.SimpleTestCase – assertions, client, Django settings ● django.test.TransactionTestCase – reset db, fixtures, more assertions ● django.test.TestCase – wraps each test in atomic block ● django.test.LiveServerTestCase – starts a real server
Django Rest Framework (DRF) ● Replicates Django’s test cases ● Replaces default client with APIClient ● RequestsAPIClient testing against an external server
● Unit tests are used to test the smallest bits of the application logic in isolation from the rest of the application ● Integration testing is used to test larger parts of the application which requires application communication with external services (db, files, queues, etc) Unit vs integration
Unit vs integration ● The same testing frameworks is usually used for both ● In most projects there is no way to run unit tests in separation from integration tests
Unit Integration API Cost Number of tests on the project
Typical unit test scenario 1. Prepare SUT (System Under the Test) ○ could be skipped in many cases in others it’s as simple as manipulating with objects in memory ○ achieve test isolation using mocking framework ○ no real IO calls (DB, queues, HTTP) 2. Run SUT logic 3. Perform assertions ○ typically using data returned back to the test
Typical integration test scenario 1. Prepare SUT ○ Reset DB, queues ○ Load fixtures – DB, files, configs, etc ○ Best practice is to avoid calling external (from the app perspective) systems – use mock servers or mock Python IO calls 2. Run SUT logic 3. Perform assertions ○ typically checking the state of the system (DB, files, etc)
Unit vs integration in Django ● Always base your TestCase class of SimpleTestCase when writing unit tests ● For integration and API tests use TestCase unless you really need to test transaction.on_commit logic in which case you need to use TransactionTestCase ● For UI tests use LiveServerTestCase
DB Fixtures (Django) ● Use dumpdata cli command to generate fixtures using data stored in the DB ● Import in TestCases fixtures class property
DB Fixtures (factory_boy) ● Simple inheritance (inc. traits) ● Generated values: sequences, lazy attributes, faker, fuzzy attributes ● Native integration with Django models ● Non-db objects (JSON structures, POPO objects, etc)
v2.0 pre-production and early days in production
pytest ● Run tests in parallel ● More flexibility around running tests ● Nicer output
pytest ● pytest --lf or pytest --ff ● pytest -k ● pytest --pdb ● markers, hooks, fixtures, etc ● plugins
assert magic
pytest-django ● --reuse-db ● set of useful django helpers
pytest-xdist ● run tests in parallel ● pytest -n auto or pytest -n ${NUM} ● pytest --looponfail (run tests on remote servers via ssh)
Tests run > 50% faster with pytest-xdist
pytest-xdist
pytest-sugar ● instafail ● nice progressbar formatting
Parameterized tests (pytest)
Parameterized tests (ddt)
Parameterized tests (ddt)
HTTP requests mocking (responses)
pyvcr
pyvcr
freezegun
V3.0 scaling (future)
Plans for the future ● Small cleanups: replace responses with vcr everywhere, small test refactoring ● Split test suite so we can run unit tests independently ● Smoke suite: migrate from Postman to pytest (or behave?) ● Add Hypothesis framework in some critical places ● Performance optimisation project: performance regression testing
Before we wrap up
Quality is everyone’s responsibility
Tests MUST run automatically in CI pipeline
End-to-end tests are important
Writing tests before code sometimes is helpful
Testing is the way not a way
But sometimes skipping testing is ok
What is not covered in this talk? ● Postman tests (monitors, smoke suite) ● Performance tests with Locust ● XFT building another Django app for testing insurance price ● UI testing with Selenium ● Behavioral testing with behave
Thanks for listening! Feedback is welcome Please reach me on Twitter @tyomo4ka

Testing Django APIs