[PT] Testar upload de arquivo

English version here.

No meu projeto, tenho um modelo que possui uma imagem de perfil. Esse campo é de preenchimento obrigatório.

Como eu não queria manter uma imagem no repositório só para teste, resolvi pesquisar outras soluções.

Arquivo de imagem na criação do modelo

Eu criei um método auxiliar para criar um ImageField que uso quando crio um objeto diretamente pelo manager do modelo:

def get_test_image_file():
    from django.core.files.images import ImageFile
    file = tempfile.NamedTemporaryFile(suffix='.png')
    return ImageFile(file, name=file.name)

O módulo tempfile do Python tem bastante coisa legal, vale dar uma olhada.

MEDIA_ROOT = tempfile.mkdtemp()

@override_settings(MEDIA_ROOT=MEDIA_ROOT)
class MeuPetTest(TestCase):
    @classmethod
    def tearDownClass(cls):
        shutil.rmtree(MEDIA_ROOT, ignore_errors=True)
        super().tearDownClass()

    def create_pet(self):
        return Pet.objects.create(profile_picture=get_test_image_file())

Podemos ver quatro coisas aí:

  • Eu crio um diretório no meu /tmp/ e adiciono o nome ao MEDIA_ROOT.
  • Usando o override_settings mudo o MEDIA_ROOT para esse diretório temporário.
  • No tearDownClass eu removo o diretório.
  • Quando crio um registro do modelo em questão, o campo da imagem recebe o ImageFile retornado pelo get_test_image_file.

Desse modo, mesmo se ficar algum arquivo de teste lá sem apagar, ao reiniciar minha máquina o sistema apaga sozinho, me poupando o trabalho de ficar deletando manualmente os arquivos de teste.

Arquivo para upload através do post

Para testes que utilizam o método post essa abordagem precisa mudar um pouco, como eu declarei um ImageField podemos ver no código do Django que ele já assume várias coisas a respeito do que vai receber nesse campo.

Para satisfazer as validações do Django o código que funcionou melhor pra mim foi esse:

@override_settings(MEDIA_ROOT=MEDIA_ROOT)
class PetRegisterTest(TestCase):
    def _create_image(self):
        from PIL import Image

        with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as f:
            image = Image.new('RGB', (200, 200), 'white')
            image.save(f, 'PNG')

        return open(f.name, mode='rb')

    def setUp(self):
        self.image = self._create_image()

    def tearDown(self):
        self.image.close()

    def test_show_registered_page(self):
        response = self.client.post(reverse('pet:register'),
                                    data={'profile_picture': self.image},
                                    follow=True)

Algumas observações sobre esse código:

  • Utilizo o override_settings igual fiz no outro trecho.
  • Uso o PIL para criar uma imagem válida e adicionar ela ao arquivo temporário que criei.
  • No context manager quando crio o arquivo temporário adiciono um argumento extra o delete. Faço isso pois o NamedTemporaryFile por padrão deleta o arquivo assim que ele é fechado, e esse não é o comportamento que eu quero.
  • Um ponto importante é o modo de leitura que utilizo pra abrir o arquivo antes de retornar, precisamos garantir que o read retornará bytes.

Essas duas abordagens funcionaram muito bem pra mim e espero que sejam úteis pra outras pessoas também. 😉

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s