Canonical Voices

What Something driven development talks about

Posts tagged with 'testing'

Michael

A number of times over the past few years I’ve needed to create some quite complex migrations (both schema and data) in a few of the Django apps that I help out with at Canonical. And like any TDD fanboy, I cry at the thought of deploying code that I’ve just tested by running it a few times with my own sample data (or writing code without first setting failing tests demoing the expected outcome).

This migration test case helper has enabled me to develop migrations test first:

class MigrationTestCase(TransactionTestCase):
    """A Test case for testing migrations."""

    # These must be defined by subclasses.
    start_migration = None
    dest_migration = None
    django_application = None

    def setUp(self):
        super(MigrationTestCase, self).setUp()
        migrations = Migrations(self.django_application)
        self.start_orm = migrations[self.start_migration].orm()
        self.dest_orm = migrations[self.dest_migration].orm()

        # Ensure the migration history is up-to-date with a fake migration.
        # The other option would be to use the south setting for these tests
        # so that the migrations are used to setup the test db.
        call_command('migrate', self.django_application, fake=True,
                     verbosity=0)
        # Then migrate back to the start migration.
        call_command('migrate', self.django_application, self.start_migration,
                     verbosity=0)

    def tearDown(self):
        # Leave the db in the final state so that the test runner doesn't
        # error when truncating the database.
        call_command('migrate', self.django_application, verbosity=0)

    def migrate_to_dest(self):
        call_command('migrate', self.django_application, self.dest_migration,
                     verbosity=0)
 

It’s not perfect – schema tests in particular end up being quite complicated as you need to ensure you’re working with the correct orm model when creating your test data – and you can’t use the normal factories to create your test data. But it does enable you to write migration tests like:

class MyMigrationTestCase(MigrationTestCase):

    start_migration = '0022_previous_migration'
    dest_migration = '0024_data_migration_after_0023_which_would_be_schema_changes'
    django_application = 'myapp'

    def test_schema_and_data_updated(self):
        # Test setup code

        self.migrate_to_dest()

        # Assertions

which keeps me happy. When I wrote that I couldn’t find any other suggestions out there for testing migrations. A quick search now turns up one idea from André (data-migrations only),  but nothing else substantial. Let me know if you’ve seen something similar or a way to improve testing of migrations.


Filed under: django, python, testing

Read more
Michael

Learning a new language is fun…finding new ways of thinking about old problems and simple ways of expressing new ideas.

As a small learning project for Golang, I set out the other day to experiment writing a simple form field validation library in my spare time – as it seems there is not yet anything along the lines of Django’s form API (email thread on go-nuts).

The purpose was to provide an API for creating forms that can validate http.Request.Form data, cleaning the data when it is valid, and collecting errors when it is not.

The initial version provides just CharField, IntegerField and RegexField, allowing form creation like:

    egForm := forms.NewForm(
        forms.NewCharField("description"),
        forms.NewIntegerField("purchase_count"))         

    egForm.SetFormData(request.Form)
    if egForm.IsValid() {
        // process the now populated egForm.CleanedData() and 
        // redirect.
    } else {
        // Use the now populated egForm.Errors map to report
        // the validation errors.
    }

The GoForms package is installable with `goinstall launchpad.net/goforms` or you can browse the goforms code on Launchpad (forms_test.go and fields_test.go have examples of the cleaned data and error). Let me know if you see flaws in the direction, or better ways of doing this in Go.

As a learning project it has been great – I’ve been able to use GoCheck for the tests, use embedded types (for sharing BaseField functionality – similar to composition+delegation without the bookkeeping) and start getting a feel for some of the subtleties of working with interfaces and other types in Go (this felt like all the benefits of z3c interfaces, without any of the overhead). Next I hope to include a small widget library for rendering basic forms/fields.


Filed under: golang, launchpad, testing

Read more
Michael

Since working with RSpec over the past 6 months – a Behaviour-Driven Development framework for ruby – I’ve been wondering if there’s anything comparable in Python (my preferred tool for development!). One of the things I love about RSpec is the ease with which Mock objects can be used to keep tests focused.

While there are a number of mock libraries around for Python most don’t result in particularly readable test code. But I was pleasantly suprised to discover Michael Foord’s Mocking and Testing utilities.

A simple example: I’ve got a Django application hosted on Google AppEngine and say I want to write a simple test to verify that my view does require the user to be logged in with their Google account and if not, redirects appropriately – but I don’t want to have to manually log a user in, or even use the Google api as part of my test. Here’s a snippet showing how easy this is with Mock:

from mock import Mockfrom google.appengine.api import users

class MySpecialView(TestCase):

    def setUp():        """ Create the required mocks for the view tests """        # Mock the get_current_user method of the google users api        users.get_current_user = Mock()

        # Create a mock user that we'll pretend is logged in        mock_user = Mock()

        # Just for readability, save the special app-engine login url as         # an instance variable        self.url = reverse('my-special-view-name')        self.login_url = "http://testserver/_ah/login?continue=http%%3A//testserver%s" % self.url

    def test_logged_in_user_can_access_page(self):        """A logged in user should not be redirected to the login page"""        # Set the return value for the mocked         # get_current_user method:        users.get_current_user.return_value = mock_user

        response = do_request()

        # Make sure the mock method was called        self.assertTrue(users.get_current_user.called)

        # And the redirect did not take place, but the         # normal template was rendered...        self.assertTemplateUsed(response, 'myapp/overview.html')

    def test_anonymous_user_is_redirected_to_login(self):        """ An anonymous user should be redirected to the login page"""        # Set the google api's get_current_user method to return None        users.get_current_user.return_value = None

        response = self.do_request()

        # Make sure the mock method was called        self.assertTrue(users.get_current_user.called)

        # And that the redirect took place... note we can't use        # the normal assertRedirects due to the app-engine specific        # login url.        self.assertEquals(response.status_code, 302)        self.assertEquals(            response['location'],            "http://testserver/_ah/login?continue=http%%3A//testserver%s" % self.url        )


Easy! Thanks Michael. The Mock object has lots of other goodies of course (such as auto-setting all the mock-methods from the real object, testing for call parameters etc.).


Read more