Aula 19 – Testes automatizados – Escrevendo nosso primeiro teste continuação

Criando uma primeira aplicação com Django e mysql

Testes automatizados 03

https://docs.djangoproject.com/pt-br/1.11/intro/tutorial05/

Testes automatizados

Testes automatizados – test-driven development

Apresentando testes automatizados

Escrevendo nosso primeiro teste

TESTE A VIEW

A aplicação de enquete da forma como está, irá publicar qualquer Question, incluindo aquelas que o campo pub_date está no futuro. Devemos refatorar o código.

Definindo um pub_date no futuro deveria fazer com que a Question não seja publicada naquele momento, mas que fique invisível até a data futura.

Para eliminar esse bug, escrevemos primeiro o teste e depois o código que o concerta.

Isso é desenvolvimento guiado por teste.

Em nosso primeiro teste, focamos no comportamento interno do código. Para este teste agora, queremos checar o comportamento da view em um navegador web, vamos testar a experiência do usuário.

Antes de tentarmos consertar alguma coisa, vamos dar uma olhada nas ferramentas à nossa disposição.

CLIENTE DE TESTES DO DJANGO

o Django fornece um test Client para simular um usuário interagindo com o código no nível da view. Podemos usá-lo em tests.py ou no shell.
Começaremos de novo com o shell, onde faremos algumas coisas que não seriam necessárias no tests.py.
A primeira é definir o ambiente de teste no shell.
python manage.py shell


>> from django.test.utils import setup_test_environment
>> setup_test_environment()

O setup_test_environment() instala um renderizador de templates o qual permite que sejam examinados alguns atributos adicionais nas respostas http tal como um response.context que de outra maneira não estariam disponíveis.

Note que este método não configura um banco de dados de teste, então o que se segue será executado no banco de dados existente e a saída pode ser diferente quanto as questões que você já criou. Você talvez tenha resultados inesperados se seu TIME_ZONE no settings.py não estiver correto. Se você não lembrou de defini-lo antes, verifique-o antes de continuar.

Em seguida, precisamos importar a classe cliente de teste (mais na frente, iremos usar a classe django.test.TestCase no tests.py, a qual traz seu próprio cliente, então, isso não será necessário):


>> from django.test import Client
>> # create an instance of the client for our use
>> client = Client()

Com isso pronto, podemos pedir para que o cliente faça algum trabalho para a gente.


>>> # get a response from '/'
>>> response = client.get('/')

Not Found: /


>>> # we should expect a 404 from that address; if you instead see an
>>> # "Invalid HTTP_HOST header" error and a 400 response, you probably
>>> # omitted the setup_test_environment() call described earlier.
>>> response.status_code

404


>>> # on the other hand we should expect to find something at '/polls/'
>>> # we'll use 'reverse()' rather than a hardcoded URL
>>> from django.urls import reverse
>>> response = client.get(reverse('polls:index'))
>>> response.status_code

200

>>> response.content

b’\n    <ul>\n    \n        <li><a href=”/polls/1/”>What&#39;s up?</a></li>\n    \n    </ul>\n\n’

>>> response.context['latest_question_list']

<QuerySet [<Question: What’s up?>]>

A lista de enquete mostra enquetes que não foram publicadas ainda (isto é aqueles que tem um pub_date no futuro). Vamos concertar isso.

No polls/views.py dentro da classe IndexView, vamos adicionar um método chamado get_queryset().

Como vamos usar timezone, precisamos importar o módulo:


from django.utils import timezone

def get_queryset(self):
    """
    Return the last five published questions (not including those set to be published in the future).
    """

    return Question.objects.filter(pub_date__lte=timezone.now()).order_by('-pub_date')[:5]

TESTANDO NOSSA NOVA VIEW

Agora nossa app ta funcionando como esperado, carregando no browser, Questions com datas no passado e no futuro, mas, listando somente aquelas que foram publicadas e não listando as com datas futuras.

Vamos criar um teste.

Adicione o seguinte a polls/tests.py  :


from django.urls import reverse

#função de atalho para criar perguntas assim como uma nova classe de teste
def create_question(question_text, days):
    """
    Create a question with the given 'question_text' and published the
    given number of 'days' offset to now (negative for questions published
    in the past, positive for questions that have yet to be published).
    """
    time = timezone.now() + datetime.timedelta(days=days)
    return Question.objects.create(question_text=question_text, pub_date=time)


class QuestionIndexViewTests(TestCase):

    def test_no_questions(self):
        """
        If no questions exist, an appropriate message is displayed.
        """
        response = self.client.get(reverse('polls:index'))
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "No polls are available.")
        self.assertQuerysetEqual(response.context['latest_question_list'], [])

    def test_past_question(self):
        """
        Questions with a pub_date in the past are displayed on the
        index page.
        """
        create_question(question_text="Past question.", days=-30)
        response = self.client.get(reverse('polls:index'))
        self.assertQuerysetEqual(response.context['latest_question_list'],['<Question: Past question.>'])

    def test_future_question(self):
        """
        Questions with a pub_date in the future aren't displayed on
        the index page.
        """
        create_question(question_text="Future question.", days=30)
        response = self.client.get(reverse('polls:index'))
        self.assertContains(response, "No polls are available.")
        self.assertQuerysetEqual(response.context['latest_question_list'], [])

    def test_future_question_and_past_question(self):
        """
        Even if both past and future questions exist, only past questions
        are displayed.
        """
        create_question(question_text="Past question.", days=-30)
        create_question(question_text="Future question.", days=30)
        response = self.client.get(reverse('polls:index'))
        self.assertQuerysetEqual(response.context['latest_question_list'],['<Question: Past question.>'])

    def test_two_past_questions(self):
        """
        The questions index page may display multiple questions.
        """
        create_question(question_text="Past question 1.", days=-30)
        create_question(question_text="Past question 2.", days=-5)
        response = self.client.get(reverse('polls:index'))
        self.assertQuerysetEqual(
        response.context['latest_question_list'],['<Question: Past question 2.>', '<Question: Past question 1.>'])
  • create_question: Crie uma pergunta (Question) com o ‘question_text’ e o número de “dias” (negativo para questões publicadas no passado, positivo para questões que ainda não foram publicadas).
  • QuestionIndexViewTests: Se não houver Questions, uma mensagem apropriada é exibida.
  • test_past_question: Questions com um pub_date no passado são exibidas na página index.
  • test_future_question:As Questions com um pub_date no futuro não são exibidas na página de índice.
  • test_future_question_and_past_question: Mesmo que existam Questions passadas e futuras, apenas as perguntas passadas são exibidas.
  • test_two_past_questions: A página index das Questions pode exibir várias questões.

TESTANDO O DETAILVIEW

O que temos funciona bem, mas, mesmo quando Questions futuras não aparecem no * index*, os usuários ainda podem acessá-las se eles conhecem ou conseguem adivinhar a URL correta. Então precisamos adicionar uma regra similar ao DetailView em polls/views.py


class DetailView(generic.DetailView):
    ...
    def get_queryset(self):
        """
        Excludes any questions that aren't published yet.
        """
        return Question.objects.filter(pub_date__lte=timezone.now())

Adicionaremos testes para verificar que uma Question a qual seu pub_date está no passado pode ser mostrada e que uma com pub_date no futuro não pode.

Então em polls/tests.py

Adicione:


class QuestionDetailViewTests(TestCase):
    
    def test_future_question(self):
        """
        The detail view of a question with a pub_date in the future
        returns a 404 not found.
        """
        future_question = create_question(question_text='Future question.', days=5)
        url = reverse('polls:detail', args=(future_question.id,))
        response = self.client.get(url)
        self.assertEqual(response.status_code, 404)

    def test_past_question(self):
        """
        The detail view of a question with a pub_date in the past
        displays the question's text.
        """
        past_question = create_question(question_text='Past Question.', days=-5)
        url = reverse('polls:detail', args=(past_question.id,))
        response = self.client.get(url)
        self.assertContains(response, past_question.question_text)

IDEIAS PARA MAIS TESTES

Devemos adicionar um método similar ao get_queryset ao ResultsView e criar uma nova classe de teste para essa “view”. Isso será muito parecido com o que criamos há pouco, de fato terá muitas repetições.

Podemos também melhorar nossa aplicação de várias outras maneiras, adicionando testes no caminho. Por exemplo é estranho que Questions possam ser publicadas no site sem que tenham Choices. Então, nossas “views” podem verificar isso e excluir tais Questions. Nossos testes podem criar uma Question sem Choices e então testar se não estão publicadas, também criar uma Question parecida com Choices, e testar se estão publicadas.

Talvez usuários registrados no admin devam ser permitidos ver Questions não publicadas, mas não os visitantes comuns. De novo: independente da necessidade a serem adicionadas ao software para que isso seja realizado, isso deve ser acompanhado por um teste, não importando se você escreve o teste primeiro e então faz o código para passar o teste, ou escreva o código da lógica primeiro e então escreva o teste para fazer a prova.

Em um certo ponto, você precisará olhar seus testes e se perguntar se o seu código está sofrendo de inchaço de testes, o que nos trás a:

QUANDO ESTIVER TESTANDO, MAIS É MELHOR

Pode parecer que nossos testes estão crescendo fora de controle. Nessa taxa teremos logo mais código nos testes do que em nossa aplicação, e a repetição não é muito estética, comparado a concisa elegância do resto do nosso código.

Não importa. Por boa parte do tempo, você pode escrever um teste uma vez e então esquecer dele. Ele vai continuar a executar sua função enquanto você continua a escrever seu programa.

Algumas vezes os testes precisarão serem atualizados. Suponha que refatoramos nossas “views” para que somente Questions com Choices sejam publicadas. Neste caso, muitos dos nossos testes existentes irão falhar nos dizendo exatamente quais testes precisam ser atualizados, assim, os próprios testes auxiliam na manutenção deles mesmos.

Na pior das hipóteses, ao continuar a desenvolver, você talvez encontre alguns testes que são reduntantes. Mesmo isso não é um problema, em testes, redundância é uma boa coisa.

Enquanto seus testes estiverem organizados sensatamente, eles não vão se tornar desorganizados.

Boas práticas incluem ter:

  • um “TestClass” separado para cada modelo ou view
  • um método de teste separado para cada conjunto de condições que você quer testar
  • nomes de métodos de teste que descrevem a sua função

MAIS TESTES

Esse tutorial apenas introduz alguns conceitos básicos de testes. Há muito mais que você pode fazer e uma série de ferramentas muito úteis à sua disposição para conseguir algumas coisas muito inteligentes.

Por exemplo, enquanto nossos testes aqui tem coberto algumas lógicas internas de um modelo e a informação publicada nas nossas “views”, você pode usar um framework para browsers como o Selenium para testar a maneira como seu HTML é processado de verdade pelo browser. Isso permite a você testar não somente o comportamente do seu código Django, mas também, por exemplo, do seu javascript. É bem legal ver seus testes iniciarem um navegador, e começar a intergir com seu site, como se fosse um ser humano guiando! O Django inclue a LiveServerTestCase para facilitar a integração com ferramentas como o Selenium.

Se você tem uma aplicação complexa, você pode querer rodar testes automaticamente com cada commit pelo propósito de integração contínua, e o próprio controle de qualidade – ao menos parcialmente – é automatizado.

Uma boa forma de encontrar partes não testadas de sua aplicação é verificar a cobertura do código, o que o código cobre de funcionalidades. Isto também ajuda a identificar códigos frágeis ou mesmo mortos. Se você não pode testar um pedaço do código, isso normalmente significa que o código precisa ser refatorado ou removido. A cobertura de testes vai ajudar a identificar código morto.

Obrigado

Até a próxima

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *