Django Utilidades :: Marinho Brandaohttp://marinhobrandao.com/pt-brTue, 06 Jan 2009 06:43:36 -0000Tomando a pílula vermelhahttp://marinhobrandao.com/blog/p/tomando-a-pilula-vermelha/<div class="document"> <p>Uma grande parte das dúvidas que chegam até mim no universo Django são relacionadas à metodologia ágil, especialmente sobre TDD (testes). A maior parte dos sintomas apontam ao caso clássico de rejeição ao ágil. É que muitas pessoas querem compreender o ágil, partindo de princípios criados por metodologias tradicionais, e isso é realmente, <strong>quase impossível</strong>.</p> <p>É preciso tomar a pílula vermelha.</p> <p>Mas o que isso significa? Significa que você deve pegar tudo o que você conhece sobre gerência de projetos e processos tradicionais aplicados a software, colocar em um latão de cobre devidamente documentado, soldar, e enterrar no CNEN <a class="footnote-reference" href="#id2" id="id1" name="id1">[1]</a> mais próximo.</p> <p>Ok, se você não é goianiense, provavelmente não pegou o trocadilho, mas não importa, o que importa é o resultado ;)</p> <p>É preciso romper com o tradicional. Não que PmBok, RUP, etc sejam totalmente errados, mas os casos onde o primeiro se aplica são relacionados à <strong>engenharia</strong>, e definitivamente, não há uma <strong>engenharia de software</strong>. Já o segundo é um baú cheio de coisas interessantes, mas que levam ao engano clássico de querer o pacote completo, e isso é realmente um problema.</p> <p>Eu estou preparando algo mais interessante sobre TDD e Django com o propósito de ajudar melhorar a compreensão da união entre as duas ferramentas, mas enquanto isso não acontece, eu gostaria de sugerir alguns links (quase) obrigatórios para quem gosta do assunto (ou para quem quer passar a ter uma vida cor-de-rosas).</p> <p>Seguem abaixo:</p> <p><strong>ImproveCast</strong></p> <ul class="simple"> <li>Ótimo podcast liderado por <strong>Vinícius Teles</strong> com entrevistas e debates sobre o assunto. Ouça especialmente o 5, com o argentino <strong>Juan Bernabó</strong>;</li> <li><a class="reference" href="http://www.improveit.com.br/podcast">http://www.improveit.com.br/podcast</a></li> </ul> <p><strong>Top 5 desculpas de quem não quer programar orientado a testes</strong></p> <ul class="simple"> <li>Não preciso dizer mais nada... hehe</li> <li><a class="reference" href="http://dojofloripa.wordpress.com/2006/11/03/top-5-desculpas-de-quem-nao-quer-programar-orientado-a-testes/">http://dojofloripa.wordpress.com/2006/11/03/top-5-desculpas-de-quem-nao-quer-programar-orientado-a-testes/</a></li> </ul> <p><strong>Testing Django Applications</strong></p> <ul class="simple"> <li>Ponto obrigatório para djangonautas utilizando testes que esclarece alguns enganos comuns da comunidade</li> <li><a class="reference" href="http://www.djangoproject.com/documentation/testing/">http://www.djangoproject.com/documentation/testing/</a></li> </ul> <p><strong>Introduction to Test Driven Design (TDD)</strong></p> <ul class="simple"> <li>Ninguém menos que <strong>Scott Ambler</strong> apresentando o TDD</li> <li><a class="reference" href="http://www.agiledata.org/essays/tdd.html">http://www.agiledata.org/essays/tdd.html</a></li> </ul> <p><strong>Wikipedia: Test-driven development</strong></p> <ul class="simple"> <li>Texto introdutório na Wikipedia</li> <li><a class="reference" href="http://en.wikipedia.org/wiki/Test-driven_development">http://en.wikipedia.org/wiki/Test-driven_development</a></li> </ul> <p><strong>Livros que introduziram o TDD (não, eu não li, mas são os mais importantes no assunto)</strong></p> <ul class="simple"> <li><a class="reference" href="http://www.amazon.com/exec/obidos/ASIN/0321146530/ambysoftinc">http://www.amazon.com/exec/obidos/ASIN/0321146530/ambysoftinc</a></li> <li><a class="reference" href="http://www.amazon.com/exec/obidos/ASIN/0131016490/ambysoftinc">http://www.amazon.com/exec/obidos/ASIN/0131016490/ambysoftinc</a></li> </ul> <p><strong>The three rules of TDD</strong></p> <ul class="simple"> <li>&quot;As três regras do TDD&quot; - para deixar claro sobre as tentativas frustradas de se escrever um teste megalomaníaco (e cair nos mesmos erros tradicionais)</li> <li><a class="reference" href="http://butunclebob.com/ArticleS.UncleBob.TheThreeRulesOfTdd">http://butunclebob.com/ArticleS.UncleBob.TheThreeRulesOfTdd</a></li> </ul> <p><strong>Scott Ambler: podcasts</strong></p> <ul class="simple"> <li>Ouça esse cara. Literalmente</li> <li><a class="reference" href="http://www.ambysoft.com/podcasts/">http://www.ambysoft.com/podcasts/</a></li> </ul> <p><strong>Martin Fowler: articles</strong></p> <ul class="simple"> <li>Também importante para estar em seu leitor RSS</li> <li><a class="reference" href="http://martinfowler.com/articles.html">http://martinfowler.com/articles.html</a></li> </ul> <p>e veja também meus links no <strong>del.icio.us</strong> relacionados ao assunto:</p> <ul class="simple"> <li><a class="reference" href="http://del.icio.us/marinho/extreme_programming">http://del.icio.us/marinho/extreme_programming</a></li> <li><a class="reference" href="http://del.icio.us/marinho/tdd">http://del.icio.us/marinho/tdd</a></li> <li><a class="reference" href="http://del.icio.us/marinho/projetos">http://del.icio.us/marinho/projetos</a></li> </ul> <p>Bom, é isso aí. E boa sorte ;)</p> <p><strong>Links relacionados</strong></p> <table class="docutils footnote" frame="void" id="id2" rules="none"> <colgroup><col class="label" /><col /></colgroup> <tbody valign="top"> <tr><td class="label"><a class="fn-backref" href="#id1" name="id2">[1]</a></td><td><a class="reference" href="http://pt.wikipedia.org/wiki/CNEN">http://pt.wikipedia.org/wiki/CNEN</a></td></tr> </tbody> </table> </div> Programação dirigida a testes no Djangohttp://marinhobrandao.com/blog/p/programacao-dirigida-a-testes-no-django/<div class="document"> <div class="section"> <h1><a id="introdu-o" name="introdu-o">Introdução</a></h1> <p>O <strong>TDD</strong> <a class="footnote-reference" href="#id15" id="id1" name="id1">[1]</a> tem crescido exponencialmente. É cada vez maior o número e a diversidade de cases utilizando essa metodologia, e esse crescimento é claramenete compreensível.</p> <p>Desenvolver baseando-se em testes é uma forma limpa e ágil de criar e mantêr software. Com ela é possível <strong>enxugar os requisitos</strong>, evitando o clássico hábito que o analista (e o cliente) tem de pedir requisitos que não serão utilizados. O TDD também <strong>facilita o projeto</strong>, dando um rumo ao implementador para que ele tenha um <strong>referencial entre certo e errado</strong>. Também ajuda na documentação e no <strong>cumprimento dos prazos</strong>.</p> <p>A <strong>Tron</strong> <a class="footnote-reference" href="#id16" id="id2" name="id2">[2]</a> já utiliza TDD em alguns projetos, especialmente no Phonus e SeLiga, onde muitas das tarefas de manutenção e desenvolvimento são feitas com base em testes de unidade, criados com <strong>dUnit</strong> <a class="footnote-reference" href="#id17" id="id3" name="id3">[3]</a>. Acontece que o <strong>Delphi</strong> não foi exatamente criado para metodologias ágeis, ele deu lá a sua contribuição para o desenvolvimento rápido, mas sempre se limitou a ser uma (boa) ferramenta. O fato de trabalhar em desktop/win32 também dificulta bastante criar determinadas rotinas de testes, sem dizer que sua época de ouro não foi a mesma do TDD.</p> <p>No Django essa realidade é diferente. Ele foi totalmente criado com base em testes, a versão do trunk possui toneladas deles. Qualquer proposta de mudança no framework só é recebida se for com os respectivos testes em anexo. E nele é possível testar praticamente tudo. Naquilo que o Django não testa (ainda - o caso do JavaScript e navegadores) o Selenium <a class="footnote-reference" href="#id18" id="id4" name="id4">[4]</a> o faz.</p> </div> <div class="section"> <h1><a id="tipos-de-testes" name="tipos-de-testes">Tipos de testes</a></h1> <p>O Django - da mesma forma que o Python - oferece duas formas distintas de testar, cada qual com suas vantagens e aplicabilidades:</p> <ul class="simple"> <li><strong>Testes de unidade (unittest)</strong> <a class="footnote-reference" href="#id19" id="id5" name="id5">[5]</a></li> <li><strong>Testes documentais (doctest)</strong> <a class="footnote-reference" href="#id20" id="id6" name="id6">[6]</a></li> </ul> <p>Esses dois módulos são parte nativa do Python e o Django não possui nenhum mérito nesse sentido. Entretanto, ele faz uso destes de uma forma bem bacana, a destacar:</p> <p><strong>manage.py test</strong></p> <p>A opção <strong>test</strong> <a class="footnote-reference" href="#id21" id="id7" name="id7">[7]</a> do <strong>manage.py</strong> percorre cada aplicação, verificando se a mesma possui um módulo de nome <strong>tests.py</strong>. Se este existir, ele será executado automaticamente.</p> <p>O mesmo vale para <em>docstrings</em> de classes de modelo e de funções e views. Qualquer uma encontrada é testada no processo.</p> <p>Ao final dos testes é exibido um relatório com os erros encontrados ou simplismente uma mensagem de <strong>&quot;Ok&quot;</strong>.</p> <p><strong>Banco de dados de testes</strong></p> <p>Ao rodar a opção de teste (<em>manage.py test</em>), o Django cria um banco de dados para os testes, e popula esse banco com os <strong>fixtures initial_data</strong> caso eles existam. Ao concluir, o banco é destruído. Isso te tranquiliza?</p> <p><strong>Test Client</strong></p> <p>Mas... e quanto ao resultado final das views? Pois bem, existe uma classe chamada <strong>django.test.client.Client</strong> que simula um navegador e retorna o resultado da url passada. É simples de utilizar e bastante eficaz, principalmente para aplicações que utilizem RESTful <a class="footnote-reference" href="#id22" id="id8" name="id8">[8]</a> ou Ajax <a class="footnote-reference" href="#id23" id="id9" name="id9">[9]</a>.</p> <p>Esta classe pode ser utilizada em doctests, testes de unidade ou no shell.</p> <p><strong>manage.py shell</strong></p> <p>Para testes rápidos, o Django também embarca na onda do IPython <a class="footnote-reference" href="#id24" id="id10" name="id10">[10]</a>: basta executar a opção pra cair num prompt já preparado para o projeto em questão. Mas tome cuidado com as modificações de dados, pois elas são feitas no banco de dados de produção.</p> </div> <div class="section"> <h1><a id="colocando-a-m-o-na-massa" name="colocando-a-m-o-na-massa">Colocando a mão na massa</a></h1> <p>Pois bem, este espaço não é grande o suficiente, portanto, vamos fazer alguns exercícios mais comuns para você ter seu friozinho na barriga.</p> <p><strong>Mas afinal, como desenvolver para testes?</strong></p> <p>O TDD se baseia em: <strong>testes antes, trabalho depois</strong>. Sinistro, não? Na verdade é uma questão de paradigmas. Um bom desenvolvedor faz seus próprios testes mentalmente enquanto lê uma ficha de caso de uso ou um requisito. É natural ouvir o analista falar e &quot;matutar&quot; um pouco sobre o assunto, buscando os pontos de gargalo e as possíveis dificuldades.</p> <p>Acontece que existe toda aquela baboseira de documentação, projeto, fichas, etc. que é um saco mas necessário, afinal todo mundo tem lá seu dia de inferno. É bom que tudo esteja devidamente anotado como e quando deve funcionar, anexado de muitas observações e atas de reunião, como diz uma boa metodologia tradicional.</p> <p>É aí que o TDD revoluciona: ele dá um basta a toda essa burocracia, pois um teste bem feito é melhor que qualquer documentação. Um teste é facilmente legível - no caso de teste documental, pode ser escrito por alguém que sequer saiba programar - e serve como projeto e documento de requisitos, ou seja: se o software passa nos testes, é porque cumpriu os requisitos.</p> <p><strong>Como fazer então?</strong></p> <p>Então, o primeiro passo é colocar a imaginação para funcionar e escrever os testes. Apesar de parecer que eles serão milhões (e eles podem realmente o ser), você vai notar que no primeiro momento não existem tantos testes assim a definir, pois não deve-se escrever testes que não estejam claros e definidos. Se não o estiver, é o caso para debater e chegar à clareza. Portanto, os primeiros testes escritos são meia-dúzia ou pouco mais que isso.</p> <p>Evidentemente esses primeiros testes não irão funcionar, já que o software sequer foi escrito. Mas é bom executá-los para ter uma primeira sensação de pisar no terreno.</p> <p>Após ter um grupo bacana de testes já implementáveis - e isso pode ser exatamente após ter sua meia-dúzia -, escreva seu software, e assim por diante: <strong>escrevendo testes, escrevendo código e refatorando-os</strong> num ciclo. Ao concluir o software, vai notar que resolveu alguns de seus maiores pesadelos, dentre eles o famoso <strong>&quot;conserta de um lado, estraga do outro&quot;</strong>, que ora realmente ocorre, ora é sinônimo de pisar em ovos ao dar manutenção.</p> <p><strong>Agora, colocando a mão na massa (sério)</strong></p> <p>Supondo que você está criando um blog (ô case maldito, todo mundo só dá exemplos de blogs!), e ele terá duas classes de modelo: Entry (artigos) e Tag. Não vamos nos ater à classe Tag, mas para testar a Entry, precisaremos dela, certo? Então dê uma olhada no TestCase que montei pra ela:</p> <pre><code># -*- encoding: utf-8 -*- import unittest from datetime import datetime from django.contrib.auth.models import User from django.core.validators import ValidationError from blog.models import Entry, Tag class EntryTestCase(unittest.TestCase): def testFields(self): entry = Entry() # Campos obrigatorios entry.title = 'Titulo' entry.description = 'Texto descritivo' entry.content = 'Texto completo' entry.author, new = User.objects.get_or_create(username='admin') # Salva entry.save() # Verifica se salvou self.assertTrue(entry.id) # Preenchido por signal self.assertTrue(entry.slug) # Valor automatico: data/hora da criação hoje = datetime.today() self.assertEquals(entry.pub_date.day, hoje.day) # Valor padrao = True self.assertTrue(entry.published) # Valor padrao = 'rest', opcoes = (('rest','reStructuredText'),('html','HTML'),('txt','Texto'),) self.assertEquals(entry.format, 'rest') # Valor padrao = 'P', opcoes = (('P','Post'),('I','Image'),('W','Wiki'),('L','Link')) self.assertEquals(entry.media_type, 'P') # Valor padrao = Null self.assertEquals(entry.image, None) # Tipo de campo = URL (com verify_exists) entry.url = 'abcd' self.assertRaises(ValidationError, entry.save) # Campo ManyToManyField t1, new = Tag.objects.get_or_create(title='django', defaults={'slug': 'django'}) t2, new = Tag.objects.get_or_create(title='python', defaults={'slug': 'python'}) entry.tags.add(t1) entry.tags.add(t2) self.assertEquals(entry.tags.count(), 2)</code></pre><p>evidentemente este case de testes não foi construído todo num só tapa. O primeiro teste foi salvar os quatro campos obrigatórios (neste caso em nem precisei me preocupar com asserção, pois o save iria soltar uma bela Exception logo de cara), depois disso fui acrescentando outros à medida que fui abstraindo... outros foram inseridos depois, quando criei campos novos.</p> <p>Para isso eu usei a biblioteca <strong>unittest</strong> e o detalhamento desta pode ser encontrado em <a class="footnote-reference" href="#id25" id="id11" name="id11">[11]</a>.</p> <p>Uma classe de testes deve sempre herdar de <strong>unittest.TestCase</strong>. Ela pode declarar os métodos <strong>setUp</strong> e <strong>tearDown</strong>, respectivamente para serem chamados antes e depois de cada método de teste. É convencional que cada método de teste inicie com <strong>'test'</strong>, e esses métodos de teste devem possuir asserções para verificar o valor correto que deve ser retornado. Essas asserções irão acusar erros quando o valor esperado não for condizente com o ocorrido.</p> <p>Para exemplo, o método <strong>assertTrue</strong> garante que o valor contido dele deve ser True, se não o for, PAU! E assim por diante. Veja maiores detalhes em <a class="footnote-reference" href="#id25" id="id12" name="id12">[11]</a>.</p> <p>Agora executando este teste através do comando <strong>manage.py test</strong>, o seguinte resultado será retornado:</p> <pre><code>Creating test database... Creating table auth_message Creating table auth_group Creating table auth_user Creating table auth_permission Creating table django_content_type Creating table django_session Creating table django_admin_log Creating table django_site Creating table django_flatpage Creating table central_userprofile Creating table blog_comment Creating table blog_entry Creating table blog_tag Installing index for auth.Message model Installing index for auth.Permission model Installing index for admin.LogEntry model Installing index for flatpages.FlatPage model Installing index for blog.Comment model Installing index for blog.Entry model Loading 'initial_data' fixtures... No fixtures found. ..F ====================================================================== FAIL: testFields (mb.blog.tests.EntryTestCase) ---------------------------------------------------------------------- Traceback (most recent call last): File "/arquivos/Projects/marinhobrandao/mb/../mb/blog/tests.py", line 47, in testFields self.assertRaises(ValidationError, entry.save) AssertionError: ValidationError not raised ---------------------------------------------------------------------- Ran 3 tests in 0.042s FAILED (failures=1) Destroying test database...</code></pre><p>não sei por que diabos a URLField não verificou a URL 'abcd' como inválida, mas não importa, fiquei satisfeito com o resultado. Se eu quiser tirar essa mensagem feia, eu tiro ou comento a asserção sobre a URL defeituosa ou verifico onde errei. Compreendeu??</p> <p><strong>Colocando a mão na massa com doctests</strong></p> <p>Agora com a Entry já criada, eu percebo a necessidade de um método <strong>'get_by_url'</strong> para verificar se uma URL é referente a um artigo e carregá-lo a partir dela. Os motivos que levaram a este requisito não são da sua conta, mas vamos colocá-lo pra testar:</p> <pre><code>class EntryManager(models.Manager): """ # Uma URL invalida >>> Entry.objects.get_by_url('http://www.terra.com.br/') # Uma URL valida >>> user, new = User.objects.get_or_create(username='admin') >>> Entry.objects.create(title='Titulo', description='Descricao', content='Conteudo', author=user) <Entry: Titulo> >>> Entry.objects.get_by_url('http://localhost:8000/blog/p/1/') <Entry: Titulo> # Uma URL valida porem a Entry nao existe >>> try: ... print Entry.objects.get_by_url('http://localhost:8000/blog/p/19/') ... except Exception, e: ... print e Entry matching query does not exist. """</code></pre><p>ao executar o mesmo comando do exemplo anterior, a mesma mensagem será exibida, ou seja, um sucesso! As regrinhas básicas do teste documental você vai encontrar em <a class="footnote-reference" href="#id20" id="id13" name="id13">[6]</a>, mas você deve estar atento a duas regras do uso de teste documental <strong>no Django</strong>:</p> <ul class="simple"> <li>serão considerados somente os testes escritos nos módulos <strong>models.py</strong> e <strong>tests.py</strong></li> <li>o módulo <strong>tests.py</strong> não deve ser necessariamente de testes de unidade, ele pode ser um grande e belo texto que se inicia e termina com três aspas seguidas, contendo testes documentais.</li> </ul> <p>o teste documental é estranho para quem não está acostumado, mas ele é bem excitante, não acha?</p> <p><strong>Colocando a mão na massa com Test Client</strong></p> <p>Para finalizar, quero dar uma breve passada na classe <strong>django.test.client.Client</strong>. Ela é bastante simples e você deve estar ciente de que o resultado de uma view pode trazer muito mais do que você está determinado a testar - toda o HTML de uma página, por exemplo.</p> <p>Mas ainda assim, ele é muito bacana e bastante prático, principalmente para views que retornem JSON, XML ou serviços RESTful.</p> <p>Você pode utilizar esta classe em testes de unidade ou testes documentais, ou mesmo no shell. É uma escolha sua.</p> <p>Esta classe dispõe de métodos bem interessantes:</p> <ul class="simple"> <li><strong>login</strong> (para efetuar login antes de testar uma view restrita)</li> <li><strong>logout</strong> (para efetuar logout após testar a view restrita que desejava)</li> <li><strong>session</strong> (sessão corrente)</li> <li><strong>get</strong> (para fazer requisições do tipo GET)</li> <li><strong>post</strong> (para fazer requisições do tipo POST)</li> </ul> <p>os dois últimos métodos retornam mais do que um <em>HTMLzão</em>, eles retornam um objeto com os seguintes atributos:</p> <ul class="simple"> <li><strong>content</strong> (o texto de retorno, em geral é HTML, mas poderia ser JSON, XML ou qualquer outro formato, vai depender de sua view)</li> <li><strong>headers</strong> (o cabeçalho HTTP retornado)</li> <li><strong>request</strong> (a request que foi criada para a requisição)</li> <li><strong>status_code</strong> (retorna o código de status retornado (o código de sucesso padrão é 200). ex.: 404)</li> <li><strong>template</strong> (o objeto de template que foi utilizado para renderizar o retorno)</li> </ul> <p>bom, devido ao tamanho que este artigo tomou e ao maior detalhamento que quero fazer sobre este último recurso, vou parar por aqui. Mas você pode dar uma olhada em <a class="footnote-reference" href="#id26" id="id14" name="id14">[12]</a> para sentir o poder que o <em>test Client</em> oferece.</p> <p>É isso aí, e tenha uma boa semana!</p> <p><strong>Links relacionados</strong></p> <table class="docutils footnote" frame="void" id="id15" rules="none"> <colgroup><col class="label" /><col /></colgroup> <tbody valign="top"> <tr><td class="label"><a class="fn-backref" href="#id1" name="id15">[1]</a></td><td><a class="reference" href="http://en.wikipedia.org/wiki/Test-driven_development">http://en.wikipedia.org/wiki/Test-driven_development</a></td></tr> </tbody> </table> <table class="docutils footnote" frame="void" id="id16" rules="none"> <colgroup><col class="label" /><col /></colgroup> <tbody valign="top"> <tr><td class="label"><a class="fn-backref" href="#id2" name="id16">[2]</a></td><td><a class="reference" href="http://www.tron.com.br/">http://www.tron.com.br/</a></td></tr> </tbody> </table> <table class="docutils footnote" frame="void" id="id17" rules="none"> <colgroup><col class="label" /><col /></colgroup> <tbody valign="top"> <tr><td class="label"><a class="fn-backref" href="#id3" name="id17">[3]</a></td><td><a class="reference" href="http://dunit.sourceforge.net/">http://dunit.sourceforge.net/</a></td></tr> </tbody> </table> <table class="docutils footnote" frame="void" id="id18" rules="none"> <colgroup><col class="label" /><col /></colgroup> <tbody valign="top"> <tr><td class="label"><a class="fn-backref" href="#id4" name="id18">[4]</a></td><td><a class="reference" href="http://www.openqa.org/selenium/">http://www.openqa.org/selenium/</a></td></tr> </tbody> </table> <table class="docutils footnote" frame="void" id="id19" rules="none"> <colgroup><col class="label" /><col /></colgroup> <tbody valign="top"> <tr><td class="label"><a class="fn-backref" href="#id5" name="id19">[5]</a></td><td><a class="reference" href="http://docs.python.org/lib/module-unittest.html">http://docs.python.org/lib/module-unittest.html</a></td></tr> </tbody> </table> <table class="docutils footnote" frame="void" id="id20" rules="none"> <colgroup><col class="label" /><col /></colgroup> <tbody valign="top"> <tr><td class="label"><a name="id20">[6]</a></td><td><em>(<a class="fn-backref" href="#id6">1</a>, <a class="fn-backref" href="#id13">2</a>)</em> <a class="reference" href="http://docs.python.org/lib/module-doctest.html">http://docs.python.org/lib/module-doctest.html</a></td></tr> </tbody> </table> <table class="docutils footnote" frame="void" id="id21" rules="none"> <colgroup><col class="label" /><col /></colgroup> <tbody valign="top"> <tr><td class="label"><a class="fn-backref" href="#id7" name="id21">[7]</a></td><td><a class="reference" href="http://www.djangoproject.com/documentation/django-admin/#test">http://www.djangoproject.com/documentation/django-admin/#test</a></td></tr> </tbody> </table> <table class="docutils footnote" frame="void" id="id22" rules="none"> <colgroup><col class="label" /><col /></colgroup> <tbody valign="top"> <tr><td class="label"><a class="fn-backref" href="#id8" name="id22">[8]</a></td><td><a class="reference" href="http://code.google.com/p/django-rest-interface/">http://code.google.com/p/django-rest-interface/</a></td></tr> </tbody> </table> <table class="docutils footnote" frame="void" id="id23" rules="none"> <colgroup><col class="label" /><col /></colgroup> <tbody valign="top"> <tr><td class="label"><a class="fn-backref" href="#id9" name="id23">[9]</a></td><td><a class="reference" href="http://pt.wikipedia.org/wiki/AJAX_(programa%C3%A7%C3%A3o">http://pt.wikipedia.org/wiki/AJAX_(programa%C3%A7%C3%A3o</a>)</td></tr> </tbody> </table> <table class="docutils footnote" frame="void" id="id24" rules="none"> <colgroup><col class="label" /><col /></colgroup> <tbody valign="top"> <tr><td class="label"><a class="fn-backref" href="#id10" name="id24">[10]</a></td><td><a class="reference" href="http://ipython.scipy.org/">http://ipython.scipy.org/</a></td></tr> </tbody> </table> <table class="docutils footnote" frame="void" id="id25" rules="none"> <colgroup><col class="label" /><col /></colgroup> <tbody valign="top"> <tr><td class="label"><a name="id25">[11]</a></td><td><em>(<a class="fn-backref" href="#id11">1</a>, <a class="fn-backref" href="#id12">2</a>)</em> <a class="reference" href="http://docs.python.org/lib/testcase-objects.html">http://docs.python.org/lib/testcase-objects.html</a></td></tr> </tbody> </table> <table class="docutils footnote" frame="void" id="id26" rules="none"> <colgroup><col class="label" /><col /></colgroup> <tbody valign="top"> <tr><td class="label"><a class="fn-backref" href="#id14" name="id26">[12]</a></td><td><a class="reference" href="http://www.djangoproject.com/documentation/testing/#the-test-client">http://www.djangoproject.com/documentation/testing/#the-test-client</a></td></tr> </tbody> </table> </div> </div>