Testing Files with Python/Django
One of the most important moments when writing a python application is testing. It could be boring, tedious and {{ insert your favorite insult here }}, but it will save your life when that careless teammate goes and twists everything (that careless teammate could be you at 7:00 a.m. on Monday) just before due time. It will also save your life with those tricky bugs, so it is a very good idea to test everything you do (or at least the most of it).
One of the subjects that I have found no useful information about is how to test files. In my case, and in this post, I was testing Images files but it will work with any kind of files, I hope.
First thing First, to not fill the server with garbage
Almost all of the few information I found about this kind of testing involved to create a file every time the test is run and let it there, all alone and lost for the rest of the eternity in one of the folders of the project (I will talk about it later). That kind of behavior I did not like because if my project involved a lot of file testing it would create a lot of garbage at my server and I would need to delete it manually. So I looked for another solution and I find it in the python tempfile library. Using this library, along with override_settings from django.test you may be able to create temporary files in your test, save it in a temporary folder and be sure that eventually your system will delete it. lets do it step by step.
First, let’s write a basic, really basic, model
from django.db import models class Picture(models.Model): picture = models.ImageField()
Then, let’s write a really, really basic, test.
from PIL import Image import tempfile from django.test import TestCase from .models import Picture from django.test import override_settings def get_temporary_image(temp_file): size = (200, 200) color = (255, 0, 0, 0) image = Image.new("RGBA", size, color) image.save(temp_file, 'jpeg') return temp_file class PictureDummyTest(TestCase): @override_settings(MEDIA_ROOT=tempfile.gettempdir()) def test_dummy_test(self): temp_file = tempfile.NamedTemporaryFile() test_image = get_temporary_image(temp_file) #test_image.seek(0) picture = Picture.objects.create(picture=test_image.name) print "It Worked!, ", picture.picture self.assertEqual(len(Picture.objects.all()), 1)
The first thing to notice is the @override_settings decorator. As its name implies, it allows to override configuration variables that will be used when running the test. In this case, the MEDIA_ROOT, the file system path where Django holds all the uploaded users files. If this path is not overridden, all the created test images will be saved there, filling our system with garbage.
So, The solution I found was to override this path with tempfile.gettempdir(); this tempfile function returns the name of the directory used for temporary files (It will return /tmp, /var/tmp, /usr/tmp or similar, depending on the OS you are using). Doing this, all the test image I create running my tests will be saved in the temporary directory and eventually will be deleted by my OS. That way, I am sure that no garbage will be left behind after the test.
The second thing to notice is tempfile.NamedTemporaryFile(), this tempfile function creates a temporary file with a visible name in the file system (a visible path where it is located). This temporary file is used by get_temporary_image function to create a small red square (It could be brown, it could be blue but I chose red, not that it really matters) and save it in that temporary file. Finally, with
picture = Picture.objects.create(picture=test_image.name)
Im telling Django “Hey, create a Picture instance with test_image as its picture field” Notice that I am using test_image.name to tell Django the path where the test_image is located. After that, Django will take that image and save a copy, related to the Picture instance it just created, at MEDIA_ROOT (that we just overrode).
When running that test, the print response is
It Worked!, /tmp/tmpIp8YS0
As you can see, the image is saved at /tmp/ so eventually the SO will delete it.
I hope this small tutorial helped you with your tests.
One last thing before I finish. If you need to test images as an attached file in an http request, you just have to use it in the request data without using the .name (because you are attaching the file, not the path where it is located) and use test_image.seek(0) to seek to the frame 0 as if the image has just been open. follow this link for more information about The PIL Image Module.
test_image.seek(0) response = self.client.put( self.reverse('upload_user_picture'), {'profile_picture': test_image})