{"id":1882,"date":"2021-11-04T13:51:59","date_gmt":"2021-11-04T11:51:59","guid":{"rendered":"http:\/\/52.91.248.125\/speeding-up-your-python-django-test-suite\/"},"modified":"2023-02-28T11:52:10","modified_gmt":"2023-02-28T09:52:10","slug":"speeding-up-your-python-django-test-suite","status":"publish","type":"post","link":"https:\/\/www.orfium.com\/engineering\/speeding-up-your-python-django-test-suite\/","title":{"rendered":"\u23f1\ufe0f Speeding up your Python &amp; Django test suite"},"content":{"rendered":"<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img decoding=\"async\" src=\"http:\/\/52.91.248.125\/wp-content\/uploads\/2023\/02\/article_01transparent-1024x731.png\" alt=\"\" class=\"wp-image-817\"\/><figcaption>Artwork by <a href=\"https:\/\/www.instagram.com\/vitzi_art\/\" data-type=\"URL\" data-id=\"https:\/\/www.instagram.com\/vitzi_art\/\" target=\"_blank\" rel=\"noreferrer noopener\">@vitzi_art<\/a><\/figcaption><\/figure><\/div>\n\n\n<h3 class=\"wp-block-heading\">Less time waiting, more time hacking!<\/h3>\n\n\n\n<p>Yes yes, we all know. Writing tests and thoroughly running them on our code is important. None of us enjoy doing it but we almost all see the benefits of this process. But what isn\u2019t as great about testing is the waiting, the context shifting and the loss of focus. At least for me, this distraction is a real drag, especially when I have to run a full test suite.<\/p>\n\n\n\n<p>This is why I find it crucial to have a fine-tuned test suite that runs as fast as possible, and why I always put some effort into speeding up my test runs, both locally and in the CI. While working on different&nbsp; Python \/ Django projects I\u2019ve discovered some tips &amp; tricks that can make your life easier. Plenty of them are included in various documentations, like the almighty <a href=\"https:\/\/docs.djangoproject.com\/en\/3.1\/topics\/testing\/\" data-type=\"URL\" data-id=\"https:\/\/docs.djangoproject.com\/en\/3.1\/topics\/testing\/\" target=\"_blank\" rel=\"noreferrer noopener\">Django docs<\/a>, but I think there\u2019s some value in collecting them all in a single place.<\/p>\n\n\n\n<p>As a bonus, I\u2019ll be sharing some examples \/ tips for enhancing your test runs when using Github Actions, as well as a case study to showcase the benefit of all these suggestions.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The quick wins<\/h3>\n\n\n\n<h4 class=\"wp-block-heading\">Running a part of the test suite<\/h4>\n\n\n\n<p>This first one is kind of obvious, but you don\u2019t have to run the whole test suite every single time. You can run tests in a single package, module, class, or even function by using the path on the test command.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&gt; python manage.py test package.module.class.function\nSystem check identified no issues (0 silenced).\n..\n----------------------------------------------------------------------\nRan 2 tests in 6.570s\n\nOK<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Keeping the database between test runs<\/h4>\n\n\n\n<p>By default, Django creates a test database for each test run, which is destroyed at the end. This is a rather slow process, especially if you want to run just a few tests! The<em><kbd> --keepdb<\/kbd><\/em> <a rel=\"noreferrer noopener\" href=\"https:\/\/docs.djangoproject.com\/en\/3.1\/ref\/django-admin\/#cmdoption-test-keepdb\" data-type=\"URL\" data-id=\"https:\/\/docs.djangoproject.com\/en\/3.1\/ref\/django-admin\/#cmdoption-test-keepdb\" target=\"_blank\">option<\/a> will not destroy and recreate the database locally on every run. This gives a huge speedup when running tests locally and is a pretty safe option to use in general.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&gt; python manage.py test &lt;path or nothing&gt; --keepdb\nUsing existing test database for alias 'default'...          &lt;--- Reused!\nSystem check identified no issues (0 silenced).\n..\n----------------------------------------------------------------------\nRan 2 tests in 6.570s\n\nOK\nPreserving test database for alias 'default'...              &lt;--- Not destroyed!<\/code><\/pre>\n\n\n\n<p>This is not as error-prone as it sounds, since every test usually takes care of restoring the state of the database, either by rolling back transactions or truncating tables. We\u2019ll talk more about this later on.<\/p>\n\n\n\n<p>If you see errors that may be related with the database not being recreated at the start of the test (like <em><kbd>IntegrityError<\/kbd><\/em>, etc), you can always remove the flag on the next run. This will destroy the database and recreate it.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Running tests in parallel<\/h4>\n\n\n\n<p>By default, Django runs tests sequentially. However, whether you\u2019re running tests locally or in your CI (Github Actions, Jenkins CI, etc) more often than not you\u2019ll have multiple cores. To leverage them, you can use the<em> <kbd>--parallel<\/kbd><\/em> <a href=\"https:\/\/docs.djangoproject.com\/en\/3.1\/ref\/django-admin\/#cmdoption-test-parallel\" data-type=\"URL\" data-id=\"https:\/\/docs.djangoproject.com\/en\/3.1\/ref\/django-admin\/#cmdoption-test-parallel\" target=\"_blank\" rel=\"noreferrer noopener\">flag<\/a>. Django will create additional processes to run your tests and additional databases to run them against.<\/p>\n\n\n\n<p>You will see something like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&gt; python3 manage.py test --parallel --keepdb\nUsing existing test database for alias 'default'...\nUsing existing clone for alias 'default'...        --\nUsing existing clone for alias 'default'...         |\nUsing existing clone for alias 'default'...         |\nUsing existing clone for alias 'default'...         |\nUsing existing clone for alias 'default'...         |\nUsing existing clone for alias 'default'...         |    =&gt; 12 processes!\nUsing existing clone for alias 'default'...         |\nUsing existing clone for alias 'default'...         |\nUsing existing clone for alias 'default'...         |\nUsing existing clone for alias 'default'...         |\nUsing existing clone for alias 'default'...         |\nUsing existing clone for alias 'default'...        -- \n\n&lt; running tests &gt; \n\nPreserving test database for alias 'default'...\n... x10 ...\nPreserving test database for alias 'default'...<\/code><\/pre>\n\n\n\n<p>In Github runners, it usually spawns 2-3 processes.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\"><p><em>When running with <kbd>--parallel<\/kbd>, Django will try to pickle tracebacks from errors to display them in the end. You\u2019ll have to add <kbd>tblib<\/kbd> as a dependency to make this work.<\/em><\/p><\/blockquote>\n\n\n\n<h4 class=\"wp-block-heading\">Caching your Python environment (CI)<\/h4>\n\n\n\n<p>Usually when running tests in CI\/CD environments a step of the process is building the Python environment (creating a virtualenv, installing dependencies etc). A common practice to speed things up here is to cache this environment and keep it between builds since it doesn\u2019t change often. Keep in mind, you\u2019ll need to invalidate this whenever your requirements change.<\/p>\n\n\n\n<p>An example for Github Actions could be adding something like this to your workflow\u2019s yaml file:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>- name: Cache pip\n  uses: actions\/cache@v2\n  with:\n    # This path is specific to Ubuntu\n    path: ${{ env.pythonLocation }}\n    # Look to see if there is a cache hit for the corresponding requirements file\n    key: ${{ env.pythonLocation }}-${{ hashFiles('requirements.txt','test_requirements.txt') }}<\/code><\/pre>\n\n\n\n<blockquote class=\"wp-block-quote\"><p>Make sure to include all your requirements files in <em><kbd>hashFiles<\/kbd><\/em>.<\/p><\/blockquote>\n\n\n\n<blockquote class=\"wp-block-quote\"><p><em>If you search online you\u2019ll find various guides, including the official Github <\/em><a href=\"https:\/\/docs.github.com\/en\/actions\/guides\/building-and-testing-python#caching-dependencies\"><em>guide<\/em><\/a><em>, advising to cache just the retrieved packages (the pip cache). I prefer the above method which caches the built packages since I saw no speedup with the suggestions there.<\/em><\/p><\/blockquote>\n\n\n\n<h3 class=\"wp-block-heading\">The slow but powerful<\/h3>\n\n\n\n<h4 class=\"wp-block-heading\">Prefer <kbd>TestCase<\/kbd> instead of <kbd>TransactionTestCase<\/kbd><\/h4>\n\n\n\n<p>Django offers 2 different base classes for test cases: <em><kbd>TestCase<\/kbd><\/em> and <em><kbd>TransactionTestCase<\/kbd><\/em>. Actually it offers more, but for this case we only care about those two.<\/p>\n\n\n\n<p>But what\u2019s the difference? Quoting the <a href=\"https:\/\/docs.djangoproject.com\/en\/3.1\/topics\/testing\/tools\/#django.test.TransactionTestCase\">docs<\/a>:<\/p>\n\n\n\n<p>Django\u2019s <em><kbd>TestCase<\/kbd><\/em> class is a more commonly used subclass of <em><kbd>TransactionTestCase<\/kbd><\/em> that makes use of database transaction facilities to speed up the process of resetting the database to a known state at the beginning of each test. A consequence of this, however, is that some database behaviors cannot be tested within a Django <em><kbd>TestCase<\/kbd><\/em> class. For instance, you cannot test that a block of code is executing within a transaction, as is required when using <kbd>select_for_update()<\/kbd>. In those cases, you should use <em><kbd>TransactionTestCase<\/kbd><\/em>.<\/p>\n\n\n\n<p><em><kbd>TransactionTestCase<\/kbd> and <kbd>TestCase<\/kbd> are identical except for the manner in which the database is reset to a known state and the ability for test code to test the effects of commit and rollback:<\/em><\/p>\n\n\n\n<ul><li>A<em> <kbd>TransactionTestCase<\/kbd><\/em> resets the database after the test runs by truncating all tables. A <em><kbd>TransactionTestCase<\/kbd><\/em> may call commit and rollback and observe the effects of these calls on the database.<\/li><li>A <em><kbd>TestCase<\/kbd><\/em>, on the other hand, does not truncate tables after a test. Instead, it encloses the test code in a database transaction that is rolled back at the end of the test. This guarantees that the rollback at the end of the test restores the database to its initial state.<\/li><\/ul>\n\n\n\n<p>In each project, there\u2019s often this one test that breaks with <em><kbd>TestCase<\/kbd><\/em> but works with <em><kbd>TransactionTestCase<\/kbd><\/em>. When engineers see this, they consider it more reliable and switch their base test classes to <em><kbd>TransactionTestCase<\/kbd><\/em>, without considering the performance impact. We\u2019ll see later on that this is not negligible at all.<\/p>\n\n\n\n<p><strong>TL;DR:<\/strong><\/p>\n\n\n\n<p>You probably only need <em>TestCase<\/em> for most of your tests. Use <em>TransactionTestCase<\/em> wisely!<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\"><p>Some additional indicators where <kbd>TransactionTestCase<\/kbd> might be useful are:<br> &#8211; emulating transaction errors<br> &#8211; using <kbd>on_commit<\/kbd> hooks<br> &#8211; firing async tasks (which will run outside the transaction by definition)<\/p><\/blockquote>\n\n\n\n<h4 class=\"wp-block-heading\">Try to use <kbd>setUpTestData<\/kbd> instead of <kbd>setUp<\/kbd><\/h4>\n\n\n\n<p>Whenever you want to set up data for your tests, you usually override &amp; use <em><kbd>setUp<\/kbd><\/em>. This runs before every test and creates the data you need.<\/p>\n\n\n\n<p>However, if you don\u2019t change the data in each test case, you can also use <em><kbd>setupTestData<\/kbd><\/em> (<a href=\"https:\/\/docs.djangoproject.com\/en\/3.1\/topics\/testing\/tools\/#django.test.TestCase.setUpTestData\" data-type=\"URL\" data-id=\"https:\/\/docs.djangoproject.com\/en\/3.1\/topics\/testing\/tools\/#django.test.TestCase.setUpTestData\" target=\"_blank\" rel=\"noreferrer noopener\">docs<\/a>). This runs once for every function in the same class and creates the necessary data. This is definitely faster, but if your tests alter the test data you can end up with weird cases. <strong>Use with caution<\/strong>.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Finding slow tests with nose<\/h4>\n\n\n\n<p>Last but not least, remember that tests are code. So there\u2019s always the chance that some tests are really slow because you didn\u2019t develop them with performance in mind. If this is the case, the best thing you can do is rewrite them. But figuring out a single slow test is not that easy.<\/p>\n\n\n\n<p>Luckily, you can use <em><kbd>django-nose<\/kbd><\/em> (<a href=\"https:\/\/pypi.org\/project\/django-nose\/\" data-type=\"URL\" data-id=\"https:\/\/pypi.org\/project\/django-nose\/\" target=\"_blank\" rel=\"noreferrer noopener\">docs<\/a>) and <em><kbd>nose-timer<\/kbd><\/em> to find the slowest tests in your suite.<\/p>\n\n\n\n<p>To do that:<\/p>\n\n\n\n<ul><li>add <em><kbd>django-nose<\/kbd><\/em>,<em> <kbd>nose-timer<\/kbd><\/em> to your requirements<\/li><li>In your<em> <kbd>settings.py<\/kbd><\/em>, change the test runner and use some nose-specific arguments<\/li><\/ul>\n\n\n\n<p>Example <em><kbd>settings.py<\/kbd><\/em>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'\nNOSE_ARGS = &#91;\n    '--nocapture',\n    '--verbosity=2',\n    '--with-timer',\n    '--timer-top-n=10',\n    '--with-id'\n]<\/code><\/pre>\n\n\n\n<p>The above arguments will make nose output:<\/p>\n\n\n\n<ul><li>The name of each test<\/li><li>The time each test takes<\/li><li>The top 10 slowest tests in the end<\/li><\/ul>\n\n\n\n<p>Example output:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>....\n#656 test_function_name (path.to.test.module.TestCase) ... ok (1.3138s)\n#657 test_function_name (path.to.test.module.TestCase) ... ok (3.0827s)\n#658 test_function_name (path.to.test.module.TestCase) ... ok (5.0743s)\n#659 test_function_name (path.to.test.module.TestCase) ... ok (5.3729s)\n....\n#665 test_function_name (path.to.test.module.TestCase) ... ok (3.1782s)\n#666 test_function_name (path.to.test.module.TestCase) ... ok (0.7577s)\n#667 test_function_name (path.to.test.module.TestCase) ... ok (0.7488s)\n\n&#91;success] 6.67% path.to.slow.test.TestCase.function: 5.3729s    ----\n&#91;success] 6.30% path.to.slow.test.TestCase.function: 5.0743s       |\n&#91;success] 5.61% path.to.slow.test.TestCase.function: 4.5148s       |\n&#91;success] 5.50% path.to.slow.test.TestCase.function: 4.4254s       |\n&#91;success] 5.09% path.to.slow.test.TestCase.function: 4.0960s       | 10 slowest\n&#91;success] 4.32% path.to.slow.test.TestCase.function: 3.4779s       |    tests\n&#91;success] 3.95% path.to.slow.test.TestCase.function: 3.1782s       |\n&#91;success] 3.83% path.to.slow.test.TestCase.function: 3.0827s       |\n&#91;success] 3.47% path.to.slow.test.TestCase.function: 2.7970s       |\n&#91;success] 3.20% path.to.slow.test.TestCase.function: 2.5786s    ---- \n----------------------------------------------------------------------\nRan 72 tests in 80.877s\n\nOK<\/code><\/pre>\n\n\n\n<p>Now it\u2019s easier to find out slow tests and debug why they take so much time to run.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Case study<\/h3>\n\n\n\n<p>To showcase the value of each of these suggestions, we\u2019ll be running a series of scenarios and measuring how much time we save with each improvement.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">The scenarios<\/h4>\n\n\n\n<ul><li>Locally<ul><li>Run a single test with \/ without<em> <kbd>--keepdb<\/kbd><\/em>, to measure the overhead of recreating the database<\/li><li>Run a whole test suite locally with \/ without<em> <kbd>--parallel<\/kbd><\/em>, to see how much faster this is<\/li><\/ul><\/li><\/ul>\n\n\n\n<ul><li>On Github Actions<ul><li>Run a whole test suite with no improvements<\/li><li>Add<em> <kbd>--parallel<\/kbd><\/em> and re-run<\/li><li>Cache the python environment and re-run<\/li><li>Change the base test case to <em><kbd>TestCase<\/kbd><\/em> from <em><kbd>TransactionTestCase<\/kbd><\/em><\/li><\/ul><\/li><\/ul>\n\n\n\n<h4 class=\"wp-block-heading\">Locally<\/h4>\n\n\n\n<h4 class=\"wp-block-heading\">Performance of <em><kbd>--keepdb<\/kbd><\/em><\/h4>\n\n\n\n<p>Let\u2019s run a single test without <em><kbd>--keepdb<\/kbd>:<\/em><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&gt; time python3 manage.py test package.module.TestCase.test \nCreating test database for alias 'default'...\nSystem check identified no issues (0 silenced).\n.\n----------------------------------------------------------------------\nRan 1 test in 4.297s\n\nOK\nDestroying test database for alias 'default'...\n\nreal    0m50.299s\nuser    1m0.945s\nsys     0m1.922s<\/code><\/pre>\n\n\n\n<p>Now the same test with <em><kbd>--keepdb<\/kbd>:<\/em><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&gt; time python3 manage.py test package.module.TestCase.test --keepdb\nUsing existing test database for alias 'default'...\n.\n----------------------------------------------------------------------\nRan 1 test in 4.148s\n\nOK\nPreserving test database for alias 'default'...\n\nreal    0m6.899s\nuser    0m20.640s\nsys     0m1.845s<\/code><\/pre>\n\n\n\n<blockquote class=\"wp-block-quote\"><p>Difference: <strong>50 sec<\/strong> vs <strong>7 sec<\/strong> or <strong>7 times faster<\/strong><\/p><\/blockquote>\n\n\n\n<h4 class=\"wp-block-heading\">Performance of &#8211;parallel<\/h4>\n\n\n\n<p>Without <kbd>--parallel<\/kbd>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&gt; python3 manage.py test --keepdb\n...\n----------------------------------------------------------------------\nRan 591 tests in 670.560s<\/code><\/pre>\n\n\n\n<p>With <kbd>--parallel<\/kbd> (concurrency: 6):<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&gt; python3 manage.py test --keepdb --parallel 6\n...\n----------------------------------------------------------------------\nRan 591 tests in 305.394s<\/code><\/pre>\n\n\n\n<blockquote class=\"wp-block-quote\"><p>Difference: <strong>670 sec<\/strong> vs <strong>305 sec<\/strong> or <strong>&gt; 2x faster<\/strong><\/p><\/blockquote>\n\n\n\n<h3 class=\"wp-block-heading\">On Github Actions<\/h3>\n\n\n\n<p>Without any improvements, the build took ~25 mins to run. The breakdown for each action can be seen below.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"http:\/\/52.91.248.125\/wp-content\/uploads\/2023\/02\/3WKMzCrdOsvZD_pcei6faLnFhGMW3qyNranfQQaJgCsbLCdIeqEG7nkacIw_NSVMh1rMOaVKukRe_14qfkz6d3ZUE7CqOBOE9ztvMZKBFP4JFoesBRhoBlE4yP4qV8pGeRMW5CHI.png\" alt=\"Note that running the test suite took 20 mins\"\/><figcaption>Note that running the test suite took 20 mins<\/figcaption><\/figure>\n\n\n\n<p>When running with <kbd>--parallel<\/kbd>, the whole build took <strong>~17 mins<\/strong> to run (<strong>~30% less<\/strong>). Running the tests took <strong>13 mins<\/strong> (vs <strong>20 mins<\/strong> without <kbd>--parallel<\/kbd>, an improvement of <strong>~40%<\/strong>).<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"http:\/\/52.91.248.125\/wp-content\/uploads\/2023\/02\/nNg5tYwpE9cwnQAdEFh9pH1xJLv9gMjkzYeGEg80cemj7yk0-M65CZai2KhXw4vQdRvDunG9QQYv-GLEuuUWn62LuRdgbbsehFgfWmV6KK3D_4CD1pWqLoHBmnxf6rQPwemLewlf.png\" alt=\"\"\/><\/figure>\n\n\n\n<p>By caching the python environment, we can see that the <em><kbd>Install dependencies<\/kbd><\/em> step takes <strong>a few seconds<\/strong> to run instead of <strong>~4 mins<\/strong>, reducing the build time to <strong>14 mins<\/strong><\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"http:\/\/52.91.248.125\/wp-content\/uploads\/2023\/02\/g54olIhunLDA0jR1U0xZHYdNGXV1OOmjZ6EZRChFiu9vzxc_zuuxIu7BJkBiyKPokDJYRt21Nf5maAOXcj_TgunDB3KvAG1v2uDLcHNHdTvnFjblGq_9NBvzSyt6-wshS5MG4oVI.png\" alt=\"\"\/><\/figure>\n\n\n\n<p>Finally, by changing the base test case from <em><kbd>TransactionTestCase<\/kbd><\/em> to <em><kbd>TestCase<\/kbd><\/em> and fixing the 3 tests that required it, the time dropped again:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"http:\/\/52.91.248.125\/wp-content\/uploads\/2023\/02\/lX73NPk10uewE6rpjH2z73-nsuQgZ36CC5owXOCcS4RNX2Hg-BnmghpBQUr-6U7FavOkukmE0tNmy7JEX65nVUWNu_FId6JIbhVxFeE5-fYYtQ6uDtXFPioXV7UIpI9f7C6ioajH.png\" alt=\"\"\/><\/figure>\n\n\n\n<p><strong>Neat, isn\u2019t it?<\/strong><\/p>\n\n\n\n<blockquote class=\"wp-block-quote\"><p><strong>Key takeaway: <\/strong>We managed to reduce the build time from ~25 mins to less than 10 mins, which is less than half of the original time.<\/p><\/blockquote>\n\n\n\n<h3 class=\"wp-block-heading\">Bonus: Using coverage.py in parallel mode<\/h3>\n\n\n\n<p>If you are using <em><kbd>coverage.py<\/kbd><\/em> setting the <em><kbd>--parallel<\/kbd><\/em> flag is not enough for your tests to run in parallel.<\/p>\n\n\n\n<p>First, you will need to set<em> <kbd>parallel = True<\/kbd><\/em> and <em><kbd>concurrency = multiprocessing<\/kbd> <\/em>to your<em> <\/em><kbd><em>.coveragerc<\/em>.<\/kbd> For example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># .coveragerc\n&#91;run]\nbranch = True\nomit = *\/__init__*\n       *\/test*.py\n       *\/migrations\/*\n       *\/urls.py\n       *\/admin.py\n       *\/apps.p\n\n# Required for parallel\nparallel = true\n# Required for parallel\nconcurrency = multiprocessing\n\n&#91;report]\nprecision = 1\nshow_missing = True\nignore_errors = True\nexclude_lines =\n    pragma: no cover\n    raise NotImplementedError\n    except ImportError\n    def __repr__\n    if self.logger.debug\n    if __name__ == .__main__.:<\/code><\/pre>\n\n\n\n<p>Then, add a<em> <kbd>sitecustomize.py<\/kbd><\/em> to your project\u2019s root directory (where you\u2019ll be running your tests from).<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># sitecustomize.py\nimport coverage\n\ncoverage.process_startup()<\/code><\/pre>\n\n\n\n<p>Finally, you\u2019ll need to do some extra steps to run with coverage and create a report.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># change the command to something like this\nCOVERAGE_PROCESS_START=.\/.coveragerc coverage run --parallel-mode --concurrency=multiprocessing --rcfile=.\/.coveragerc manage.py test --parallel\n# combine individual coverage files \ncoverage combine --rcfile=.\/.coveragerc\n# and then create the coverage report\ncoverage report -m --rcfile=.\/.coveragerc<\/code><\/pre>\n\n\n\n<p><strong>Enjoy your way faster test suite!<\/strong>  <br><\/p>\n\n\n\n<div class=\"wp-block-columns is-layout-flex wp-container-core-columns-layout-1 wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\" style=\"flex-basis:33.33%\">\n<figure class=\"wp-block-image size-thumbnail is-style-rounded\"><img decoding=\"async\" src=\"http:\/\/52.91.248.125\/wp-content\/uploads\/2023\/02\/150382275_3727031577389685_3450808424313451905_o-1-150x150.jpg\" alt=\"\" class=\"wp-image-833\"\/><\/figure>\n<\/div>\n\n\n\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\" style=\"flex-basis:50%\">\n<p><strong>Sergios Aftsidis<\/strong><\/p>\n\n\n\n<p>Senior Backend Software Engineer @ \u039fRFIUM<\/p>\n\n\n\n<p><a href=\"https:\/\/www.linkedin.com\/in\/saftsidis\/\">https:\/\/www.linkedin.com\/in\/saftsidis\/<\/a><\/p>\n\n\n\n<p><a href=\"https:\/\/iamsafts.com\/\">https:\/\/iamsafts.com\/<\/a><\/p>\n\n\n\n<p><a href=\"https:\/\/github.com\/safts\">https:\/\/github.com\/safts<\/a><\/p>\n<\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Less time waiting, more time hacking! Yes yes, we all know. Writing tests and thoroughly running them on our code is important. None of us enjoy doing it but we almost all see the benefits of this process. But what isn\u2019t as great about testing is the waiting, the context shifting and the loss of [&hellip;]<\/p>\n","protected":false},"author":11,"featured_media":1890,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_seopress_robots_primary_cat":"none","_seopress_titles_title":"","_seopress_titles_desc":"","_seopress_robots_index":"","content-type":"","footnotes":""},"categories":[16,19],"tags":[],"acf":[],"_links":{"self":[{"href":"https:\/\/www.orfium.com\/wp-json\/wp\/v2\/posts\/1882"}],"collection":[{"href":"https:\/\/www.orfium.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.orfium.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.orfium.com\/wp-json\/wp\/v2\/users\/11"}],"replies":[{"embeddable":true,"href":"https:\/\/www.orfium.com\/wp-json\/wp\/v2\/comments?post=1882"}],"version-history":[{"count":1,"href":"https:\/\/www.orfium.com\/wp-json\/wp\/v2\/posts\/1882\/revisions"}],"predecessor-version":[{"id":1889,"href":"https:\/\/www.orfium.com\/wp-json\/wp\/v2\/posts\/1882\/revisions\/1889"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.orfium.com\/wp-json\/wp\/v2\/media\/1890"}],"wp:attachment":[{"href":"https:\/\/www.orfium.com\/wp-json\/wp\/v2\/media?parent=1882"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.orfium.com\/wp-json\/wp\/v2\/categories?post=1882"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.orfium.com\/wp-json\/wp\/v2\/tags?post=1882"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}