Managing Django test isolation for installable apps - django
I maintain an installable Django app that includes a regular test suite.
Naturally enough when project authors run manage.py test for their site, the tests for both their own apps and also any third party installed apps such as mine will all run.
The problem that I'm seeing is that in several different cases, the user's particular settings.py will contain configurations that cause my app's tests to fail.
A couple of examples:
Some of the tests need to check for returned error messages. These error messages use the internationalization framework, so if the site language is not english then these tests fail.
Some of the tests need to check for particular template output. If the site is using customized templates (which the app supports) then the tests will end up using their customized templates in preference to the defaults, and again the tests will fail.
I want to try to figure out a sensible approach to isolating the environment that my tests get run with in order to avoid this.
My plan at the moment is to have all my TestCase classes extend a base TestCase, which overrides the settings, and any other environment setup I may need to take care of.
My questions are:
Is this the best approach to app-level test-environment isolation? Is there an alternative I've missed?
It looks like I can only override a setting at a time, when ideally I'd probably like a completely clean configuration. Is there be a way to do this, and if not which are the main settings I need to make sure are set in order to have a basic clean setup?
I believe I'm correct in saying that overriding some settings such as INSTALLED_APPS may not actually affect the environment in the expected way due to implementation details, and global state issues. Is this correct? Which settings do I need to be aware of, and what globally cached environment information may not be affected as expected?
What other environment state other than settings might I need to ensure is clean?
More generally, I'd also be interested in any context regarding how much of an issue this is for other third party installable apps, or if there are any plans to further address any of this in core. I've seen conversation on IRC regarding similar issues with eg. some of Django's contrib apps running under unexpected settings configurations. I seem to also remember running into similar cases with both third party apps and django contrib apps a few times, so it feels like I'm not alone in facing these kind of problems, but it's not clear if there's a consensus on if this is something that needs more work or if the status quo is good enough.
Note that:
These are integration-level tests, so I want to address these environment issues at the global level.
I need to support Django 1.3, but can put in some compatibility wrappers so long as I'm not re-implementing massive amounts of Django code.
Obviously enough, since this is an installable app, I can't just specify my own DJANGO_SETTINGS_MODULE to be used for the tests.
A nice approach to isolation I've seen used by Jezdez is to have a submodule called my_app.tests which contains all the test code (example). This means that those tests are NOT run by default when someone installs your app, so they don't get random phantom test failures, but if they want to check that they haven't inadvertently broken something then it's as simple as adding myapp.tests to INSTALLED_APPS to get it to run.
Within the tests, you can do your best to ensure that the correct environment exists using override_settings (if this isn't in 1.4 then there's not that much code to it). Personally my feeling is that with integration type tests perhaps it doesn't matter if they fail. If you like, you can include a clean settings file (compressor.test_settings), which for a major project may be more appropriate.
An alternative is that you separate your tests out a bit - there are two separate bodies of tests for contrib.admin, those at django.contrib.admin.tests, and those at tests.regression_tests.contrib.admin (or some path like that). The ones to check public apis and core functionality (should) reside in the first, and anything likely to get broken by someone else's (reasonable) configuration resides in the second.
IMHO, the whole running external apps tests is totally broken. It certainly shouldn't happen by default (and there are discussions to that effect) and it shouldn't even be a thing - if someone's external app test suite is broken by my monkey patching (or whatever) I don't actually care - and I definitely don't want it to break the build of my site. That said, the above approaches allow those who disagree to run them fairly easily. Jezdez probably has as many major pluggable apps as anyone else, and even if there are some subtle issues with his approach at least there is consistency of behaviour.
Since you're releasing a reusable third-party application, I don't see any reason the developer using the application should be changing the code. If the code isn't changing, the developers shouldn't need to run your tests.
The best solution, IMO, is to have the tests sit outside of the installable package. When you install Django and run manage.py tests, you don't run the Django test suite, because you trust the version of Django you've installed is stable. This should be the same for developers using your third-party application.
If there are specific settings you want to ensure work your library, just write test cases that use those settings values.
Here's an example reusable django application that has the tests sit outside of the installed package:
https://github.com/Yipit/django-roughage/tree/master
It's a popular way to develop python modules as seen:
https://github.com/kennethreitz/requests
https://github.com/getsentry/sentry
Related
Mocking ES Modules when running the Vite development server
I need to find out how I can instruct Vite to replace references to local/relative modules at runtime. The use case here is the test runner mocha-vite-puppeteer, which uses Vite to run tests, but then stubbing of modules of course does not work when using Node machinery such as proxyquire or rewire. So I basically need to either be tipped of some existing software that can help me in doing this, or some tips on how to create my own "vite-proxyquire" using import.meta and friends. A normal use for temporarily stubbing out ./my-ugly-module might be that you want to avoid loading some sub-dependency that has some ugly transitive dependencies that suck the entire application tree into your little test, or you want to avoid loading a sub-dependency that has some ugly side effects on the global state. Existing solutions Modern Web, a refreshing "bundler- and frameworkless" approach to web development using standard tools, talk a bit about the issue around how the immutable nature of ES Modules prevent usual stubbing patterns. They present a solution in the form of Import Maps, which essentially would be similar to the alias config in Vite (Rollup really), mapping a path to a module to some other file. The problem with a static solution like this is that it would replace all imports of a given module, not just for a single test. Modern Web has a solution to this where they have chosen to use a custom html page for each such test. To make this less of a hassle with regards to running, they then have a custom test runner that handles dealing with all these extra test html files. Doing something like that could be one way of fixing it, but it would require developing quite a bit of middleware/plugin code IMHO to make it work transparently with Vite. Without any advanced tooling it would also introduce a lot of extra files that seems a bit of a downside compared to todays imperative mocking of dependencies with proxyquire, Jest or Test Double from inside of the test files.
Have different code execute depending on lein profiles?
I want my code to behave a tiny bit differently in development than in production; for example, don't actually post things on facebook when the dev profile is activated. Right now I'm thinking I can use robert-hooke to add hooks to functions I don't want run in development; however, how can I check which profiles are activated? I've also checked out environ which looks great for development vs production configurations but doesn't seem to hit my problem. I don't think this is a rare problem so if there's already some accepted ways to handle this; great.
If you take a look at the luminus guestbook example, it's actually using profiles to set an environment variable :dev, and then environ to read it back from within the application. Environ suggests using the 12 factor app as a model, which makes an argument against grouping configurations inside of the application. Leiningen let's us have the best of both by naming the configuration group external to the actual application. Unfortunately the variable passed to the application is named the same as the profile, and thus groups configurations in the app. Naming it cache.disable but leaving it in the dev profile could fix that. You could also take a look at isolating dependencies for development. The article has an example near the end using System/getenv that could also use environ as a replacement.
What's the best way to install a standalone djangopypi?
I would like to install a djangopypi server for our local development and deployment. However, I'm slightly confused about its installation docs. Apparently, it's assumed that djangopypi is installed inside a bigger project as an app, which is at least debatable from my point of view. I would like my local PyPI instance to run independently of anything else, as a "normal" web service. And this is where I'm lost. It seems I need some kind of a minimal Django project to wrap djangopypi, which seems a bit overkill for me. Is there a more elegant way to install it in standalone mode?
That's exactly what you need. djangopypi is just an app. Like any Django app, it needs to know stuff like how to connect to your database, etc. That information comes from the project. It doesn't provide this for you because there's no way it could possible know what the best settings are for your particular environment; that's your responsibility. So, no, it's not "overkill". It's the bare minimum required for functionality, and it's just the way things are. Create a simple project, change all the relevant items in settings.py, nclude djangopypi's urls.py in yours, and you're done. Is is really that hard?
Django test organization questions
In the interest of decoupled code, I've created several apps in my project that can exist without the others being there. Any app can be removed from the project without breaking things later down the line. To do this, I've created some tests that make use of the #override_settings decorator in Django 1.4, however, I would like to test the functionality of the apps and their interaction together. So, I would like to have tests that do not make the apps depend on each other, but I would also like to have tests that test the project as a whole. Where is the normal place to store these? Are there any tricks to doing this?
I'm not aware of any established convention, but what I usually do is that I create an app called, for example, tests and place the higher-level, integration tests there.
Setting up proper testing for Django for TDD
I've been ignoring the need to test my project for far to long. So I spent more than a day looking for ways to implement tests for my current apps and trying to get some TDD going for new apps. I found a lot of "tutorials" with the steps: "1. Install this 2. Install that 3. Install thisnthat 4. Done!", but noone seems to talk about how to structure your tests, both file and code wise. And noone ever talks about how to set up a CI server, or just integrate the testing with the deployment of your project. A lot of people mention fabric, virtualenv and nose - but noone describes how they work with them together as a whole. What I keep finding is detailed information about how you set up a proper Rails environment with testing and CI etc... Does anyone else feel that the Django community lacks in this area, or is it just me? :) Oh, and does anyone else have any suggestions on how to do it?
As I see it, there are several parts to the problem. One thing you need are good unit tests. The primary characteristic of unit tests is that they are very fast, so that they can test the combinatorial possibilities of function inputs and branch coverage. To get their speed, and to maintain isolation between tests, even if they are running in parallel, unit tests should not touch the database or network or file system. Such tests are hard to write in Django projects, because the Django ORM makes it so convenient to scatter database access calls throughout your product code. Hence any tests of Django code will inevitably hit the database. Ideally, you should approach this by limiting the database access in your product code to a thin data access layer built on top of the django ORM, which exposes methods pertinent to your application. Another approach is for your tests to mock out the ORM calls. In the worst case, you will give up on this: Your unit tests become integration tests: They actually hit the database, cross multiple layers of your architecture, and take minutes to run, which discourages developers from running them frequently enough. The implication of this is that writing integration tests is easy - the canonical style of Django tests covers this perfectly. The final, and hardest part of the problem, is running your acceptance tests. The defining characteristic of acceptance tests is that they invoke your application end-to-end, as a user does in production, to prove that your application actually works. Canonical dhango tests using the django testrunner fall short of this. They do not issue actually HTTP requests (instead, they examine the url config to figure out what middleware and view would get called to handle a particular request, and then they call it, in process.) This means that such tests are not testing your webserver config, nor any javascript, or rendering in the browser, etc. To test this, you need something like selenium. Additionally, we have many server-side processes, such as cron jobs, which use code from our Django project. Acceptance tests which involve these processes should invoke the jobs just like cron does, as a new process. Both these scenarios have some problems. Firstly, you can't simply run such tests under the Django test runner. If you try to do so, then you will find that the test data you have written during the test setup (either using the django fixtures mechanism, or by simply calling "MyModel().save()" in a test) are in a transaction which your product code, running in a different process, is not party to. So your tests have to commit the changes they make before the product code can see them. This interferes with the clean-up between tests that Django's testrunner helpfully does, so you have to switch it into a different mode, which does explicit deletes rather than rolling back. Sadly, this is much slower. At last night's London Django user group, a couple of Django core developers assured me that this scenario also has other complications (which I confess I don't know what they are), which it is best to avoid by not running acceptance tests within the Django test runner at all, but creating them as a completely stand-alone set of tests. If you do this, then your immediate problem is that you have lost the benefits the Django test runnner provides: Namely it creates a test database, and cleans it between each test. You will have to create some equivalent mechanism for yourself. You will need your product code to run against a test database if it is being invoked as part of a test. You need to be absolutely certain that if product code is run as part of a test, even on a production box, then it can NEVER accidentally touch the production database, so this mechanism has to be absolutely failsafe. Forgetting to set an environment variable in a test setup, for example, should not cause a blooper in this regard. This is all before even considering the complications that arise from deployment, having parts of your project in different repos, dependent on each other, creating pip-installable packages, etc. All in all, I'd love to hear from someone who feels they have found a good solution to this problem. It is far from a trivial issue as some commenters imply.
Harry Percival is creating a Django / TDD / Selenium tutorial (and accompanying workshop, if you live in London.) His book reads like a hands-on tutorial, and goes into great detail on the subject: https://www.obeythetestinggoat.com/book/part1.harry.html
In my experience, fine-grained unit tests for web apps are not worth it, the setup/teardown is too expensive and the tests are too fragile. The only exception is isolated components, especially those with clear inputs & outputs and complicated algorithms. Do unit-test those to the smallest details. I had the best testing experience using a semi-functional testing tool called testbrowser, which simulates browser actions in Python. For integration with Django, install the homophony app (disclaimer: I am the author of the app). Testbrowser may be a little too coarse for test-driven development, but it's the best testing tool of the ones I have used so far. Most importantly, it scales up fairly well, whereas unit tests and browser-based functional test tools tend to become very brittle as your app grows in size. As for a CI tool, go with Buildbot or Jenkins.
I use a combination of Django's excellent extension of the python unittest framework for testing api's / models / helper functions, and selenium for in browser testing. Selenium has great instructions for how to set it up and write tests in python.