Historique d'apprentissage pour participer au développement d'applications d'équipe avec Python ~ Tutoriel Django 5 ~

introduction

Cette fois, je vais aborder les tests en dehors des fonctions de l'application.

À propos du test

J'avais l'habitude de faire ce genre de chose pour obtenir un emploi, mais quand je l'ai rendu public et que le responsable le voit lors de l'entretien, ou de la personne qui l'a touché, le cadre De nombreuses personnes ont souligné qu'elles ne comprenaient pas suffisamment. L'un d'eux était qu'il n'y avait pas de code de test.

Extrait du tutoriel officiel

Pourquoi vous devez créer un test Pourquoi faire un test? Et pourquoi maintenant? Peut-être êtes-vous trop occupé à apprendre Python ou Django, et apprendre quelque chose d'autre peut sembler intimidant et inutile. C'est parce que l'application de vote fonctionne bien et que l'introduction de tests automatisés n'améliore pas l'application. Si le seul but de l'apprentissage de la programmation Django est de créer cette application de vote, je suis sûr que vous n'avez pas besoin d'implémenter des tests automatisés. Mais sinon, c'est le moment idéal pour en savoir plus sur les tests automatisés.

Le tutoriel officiel présente également le test de cette manière. Alors pourquoi écrire un test

J'ai senti qu'il était grossièrement divisé en ces deux points. Le premier est important du point de vue de ce que l'on appelle l'exhaustivité et la maintenance. Surtout si vous êtes un débutant comme nous

Extrait du tutoriel officiel

La rédaction de tests peut vous aider à collaborer en équipe. Jusqu'à présent, j'ai écrit du point de vue d'un développeur qui gère l'application. Cependant, les applications complexes deviennent gérées par l'équipe. Les tests vous protègent contre la rupture accidentelle du code que vous écrivez (et également contre la rupture du code écrit par d'autres). Si vous envisagez de vivre en tant que programmeur Django, vous devez absolument écrire de bons tests!

J'ai tendance à manquer de ce genre de réflexion, mais même si je le fais moi-même, je le ferai en équipe parce que le moi d'hier, ma faute de frappe il y a quelques heures et un code étrange gêneront le code que j'ai écrit maintenant. Quand il s'agit de cela, il n'est pas difficile d'imaginer qu'il y a un risque.

Concernant ce dernier, j'ai senti que ce n'était qu'après le test que je pouvais m'appeler codeur. Allons-nous en.

Quand faut-il créer un test?

  1. Opinion d'écrire un test puis d'écrire du code → Il est basé sur la théorie selon laquelle si vous écrivez un test, vous pouvez écrire du code. Clarifiez le problème avec le processus que vous souhaitez réaliser avant de commencer à écrire du code.

  2. Opinion pour écrire du code puis tester → Écrivez selon la théorie de vérifier si le code que vous avez écrit fonctionne normalement.

  3. Ajoutez de nouvelles fonctionnalités, vérifiez les bogues et modifiez les composants existants.

Je ne sais pas quelle est la bonne réponse, mais je pense que la personne qui peut faire 1 est déjà un bon programmeur. Ensuite, je pense que ce que nous recherchons les débutants, c'est d'être conscients d'aller à la troisième étape, qui est un compromis entre 1 et 2.

Prendre le temps d'écrire un test est un bon détour du point de vue de simplement «faire quelque chose qui bouge», et c'est un endroit où les débutants se sentent mal.

Cependant, comme je l'ai dit à plusieurs reprises, si vous ne pouvez pas écrire un test, vous ne pouvez pas vous empêcher d'être évalué par d'autres personnes qui ne comprennent pas le code que vous avez écrit, donc la première chose à faire pour en sortir. Je pense que cela correspond à 3.

Écrivons un code de test pour la vérification des bogues.

Maintenant, faisons les éléments de test du tutoriel Django tout en gardant à l'esprit ce que nous avons écrit dans 3. Rappelez-vous qu'il y avait un bogue dans models.py dans précédemment? C'est un bogue que la soi-disant ** date future ** est affichée.

Exécutez la commande suivante à l'aide de la commande shell.

import datetime
from django.utils import timezone
from polls.models import Question

future_question = Question(pub_date=timezone.now() + datetime.timedelta(days=30)) #1
future_question.was_published_recently() #2

Dans 1, la méthode timedelta est utilisée pour créer une instance d'une date future. Puisque pub_date = timezone.now () + datetime.timedelta (days = 30), nous publierons une instance avec des informations de date 30 jours après la date actuelle.

En 2, la méthode was_published_recently () est exécutée en utilisant cette instance. Qu'est-ce que tu fais

return self.pub_date >= timezone.now() - datetime.timedelta(days=1)

C'était une méthode qui renvoie «True» si les informations de date de l'instance ne sont pas dans le passé, sinon elle renvoie «False». Cependant, il a été dit qu'il y a un bogue selon lequel ce sera "True" même s'il s'agit d'une date future. Donc, le résultat de 2 est également "Vrai". Créons maintenant un test pour trouver ce bogue.

polls/tests.py


import datetime

from django.test import TestCase
from django.utils import timezone

from .models import Question


class QuestionModelTests(TestCase):

    def test_was_published_recently_with_future_question(self):

        time = timezone.now() + datetime.timedelta(days=30)
        future_question = Question(pub_date=time)
        self.assertIs(future_question.was_published_recently(), False)

Le fichier qui décrit les tests dans Django est tests.py. Importons l'objet TestCase au début. La façon d'écrire un test est de créer une classe qui hérite de django.test.TestCase et d'y écrire la méthode. D'ailleurs, en même temps que l'héritage de «TestCase», il hérite également de «TransactionTestCase» et «SimpleTestCase». Pour les applications qui n'utilisent pas de base de données, il semble préférable d'hériter uniquement de «SimpleTestCase». Donc, pour faire simple, ce que fait ce TestCase, c'est que lorsque tests.py est exécuté, il commence à rechercher des classes qui héritent de TestCase. Et quand tu le découvre

  1. Créez une base de données pour tester et appliquez la migration.
  2. Recherchez et exécutez la méthode qui commence par test.
  3. Revenez en arrière.
  4. Répétez les étapes 2 et 3 s'il existe d'autres méthodes.
  5. Revenez à avant d'exécuter la classe TestCase.
  6. Supprimez la base de données créée en 1.

Est effectuée. En d'autres termes, on peut dire que le test et chaque méthode du test sont quelque peu indépendants du point de vue de l'application. Référence 1 Référence 2

Ensuite, que fait la méthode test_was_published_recently_with_future_question?

  1. Attribuez une date future à la variable «time».
  2. Affectez l'instance «Question» avec les informations de date de la variable «time» à la variable «future_question».
  3. ʻassertIs (a, b) vérifie que ʻa = b, c'est-à-dire quefuture_question.was_published_recently ()est False.

Cela signifie que. Maintenant que vous comprenez cela, exécutons le test.

py manage.py test polls

Si vous avez plusieurs applications, remplacez les sondages par n'importe quel nom de dossier d'application. Comme résultat d'exécution

System check identified no issues (0 silenced).
F
======================================================================
FAIL: test_was_published_recently_with_future_question (polls.tests.QuestionModelTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/path/to/mysite/polls/tests.py", line 16, in test_was_published_recently_with_future_question
    self.assertIs(future_question.was_published_recently(), False)
AssertionError: True is not False

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (failures=1)
Destroying test database for alias 'default'...

Vous devriez voir quelque chose comme ça. Vous pouvez voir ici que was_published_recently () renvoie True pour une date future, car nous obtenons l'erreur ʻAssertionError: True is not False`. Vous avez trouvé un bug.

Corrections de bogues

Maintenant que nous avons trouvé un bogue, nous allons écrire du code pour le corriger.

# return self.pub_date >= timezone.now() - datetime.timedelta(days=1)Vers le suivant

def was_published_recently(self):
    now = timezone.now()
    return now - datetime.timedelta(days=1) <= self.pub_date <= now

Si vous souhaitez renvoyer «True» à une date ultérieure, vous pouvez ajouter la date actuelle à la spécification de condition et la définir sur la valeur maximale.

Au fait, quand il s'agit de tests rigoureux

polls/tests.py


def test_was_published_recently_with_old_question(self):
    """
    was_published_recently() returns False for questions whose pub_date
    is older than 1 day.
    """
    time = timezone.now() - datetime.timedelta(days=1, seconds=1)
    old_question = Question(pub_date=time)
    self.assertIs(old_question.was_published_recently(), False)

def test_was_published_recently_with_recent_question(self):
    """
    was_published_recently() returns True for questions whose pub_date
    is within the last day.
    """
    time = timezone.now() - datetime.timedelta(hours=23, minutes=59, seconds=59)
    recent_question = Question(pub_date=time)
    self.assertIs(recent_question.was_published_recently(), True)

Définissez les deux nouvelles méthodes ci-dessus dans tests.py. La méthode test_was_published_recently_with_old_question () est une méthode qui vérifie quewas_published_recently ()a renvoyé True à la date passée. La méthode test_was_published_recently_with_recent_question () est une méthode qui vérifie quewas_published_recently ()renvoie True à la date actuelle.

Écrire un code de test (Afficher)

Maintenant que nous avons testé l'algorithme, vous vous demanderez s'il fonctionne réellement dans votre application. Donc, cette fois, j'écrirai un test pour View.

Tout d'abord, ajoutez les modules et classes de test suivants à la classe que vous avez créée précédemment.

pols/tests.py


from django.urls import reverse

#Omission

def create_question(question_text, days):
    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):
        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):
    	#Créer une instance de question avec une date passée
        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):
        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):
        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):
        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.>']
        )

La méthode create_question est une méthode pour créer un objet Question qui prend question_text et jours comme arguments. Puisque l'objet Question sera créé dans les classes de test suivantes, définissez-le au début pour ne pas avoir à écrire le code à chaque fois.

Jetons un coup d'œil à la classe QuestionIndexViewTests. La classe client qui apparaît tout au long de l'histoire est une classe utilisée pour les tests et est une classe qui simule l'accès dans le navigateur. Référence Par exemple, écrire «self.client.get» testera le traitement réel de la demande comme «self.objects.get».

test_no_questions est une méthode pour vérifier le comportement lorsque l'objet Question n'existe pas. Cliquez maintenant sur l'URL avec 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'], [])

Les trois ci-dessus vérifient les arguments de la même manière que la méthode ʻassertIs () mentionnée précédemment. Par exemple, self.assertEqual (response.status_code, 200)vérifiera si la page est affichée dans le navigateur après avoir frappé l'URL plus tôt. Puisque le code d'accès de 200 est émis lorsque la page est affichée normalement, nous vérifionsresponse.status_code == 200. En passant, lors de la vérification du fonctionnement de POST tel que formulaire, ce sera self.client.post`.

self.assertContains () vérifie si l'argument contient celui spécifié dans le deuxième argument. Par exemple, dans ce cas, context est inclus dans la valeur de retour de self.client.get (reverse ('polls: index')) ʻin response`, donc la chaîne de caractères spécifiée. Il sera vérifié s'il y en a.

self.assertQuerysetEqual () vérifie s'il y a des données dans le jeu de requêtes. À propos, l'ensemble de requêtes fait référence au type de données comme «int», et plus grosso modo, pensez-y comme une série d'informations extraites du modèle. Cette fois, nous avons frappé l'URL du chemin de polls: index, donc la classe ʻIndexView sera exécutée. Ensuite, dans la classe ʻIndexView, les données sont finalement extraites de la base de données par la méthodeget_queryset (), donc l'ensemble de requêtes y est né. Au fait Puisque self.assertQuerysetEqual (response.context ['latest_question_list'], []), dans ce cas, cela signifie que latest_question_list est vide, c'est-à-dire qu'il vérifie qu'il n'y a pas de questions.

Il en va de même pour les méthodes d'essai suivantes. Créez un objet Question en spécifiant les valeurs de question_text et days comme arguments de la méthodecreate_question ()définie et testez en fonction de celle-ci. Concernant le résultat de l'exécution de response.context (), dans le modèle

<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>

Depuis que j'ai fait une telle description, j'ai spécifié question_text comme argument, donc je me souviens que celui qui y est spécifié est entré dans {{question.question_text}}. Ainsi, par exemple, la méthode test_past_question () placera ici la `question précédente. Donc

self.assertQuerysetEqual(response.context['latest_question_list'],['<Question: Past question.>'])

Il est important de comprendre qu'une telle déclaration signifie que la valeur de retour vérifie que le contexte html à l'intérieur de la «réponse» contient la «question passée». Si vous comprenez jusqu'ici, dans test_future_question ()

self.assertQuerysetEqual(response.context['latest_question_list'], [])

Je ne pense pas que ce soit difficile parce que vous pouvez voir que c'est vrai.

Enfin, modifiez View avant de tester. J'ai corrigé le bug du modèle, mais c'est parce qu'il se retrouverait avec un objet avec une date future. Importez l'objet timezone du module django.utils et modifiez la classe ʻIndexView` comme suit:


from django.utils import timezone

class IndexView(generic.ListView):
	template_name = 'polls/index.html'
	context_object_name = 'latest_question_list'

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

Pensez à pub_date__lte = timezone.now () comme synonyme depub_date <= timezone.now (). En d'autres termes, le processus renvoie un ensemble de requêtes qui récupère 5 objets «Question» avec des informations de date antérieures à la date actuelle.

Test DetailView

Bien qu'il soit correct de corriger jusqu'à ʻIndex, les données de la date future seront toujours affichées dans DetailView` telles quelles, donc corrigeons-les également ici. Modifiez comme suit.

polls/views.py



class DetailView(generic.DetailView):
    ...
    def get_queryset(self):
        return Question.objects.filter(pub_date__lte=timezone.now())

Ensuite, ajoutez la classe de test suivante à tests.py.

class QuestionDetailViewTests(TestCase):
    def test_future_question(self):
        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):
        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

Fondamentalement, la théorie est la même que le test précédent. La différence est le test DetailView, alors préparez la partie 34 de l'URL telle que / polls / 34 à l'avance sous la forme ʻurl = reverse ('polls: detail', args = (future_question.id,))`. Vous devez le spécifier en tant qu'URL distincte.

Et s'il y a trop de tests?

Au fait, certains d'entre vous l'ont peut-être remarqué en écrivant des tests jusqu'à présent, mais par exemple, dans le cas de l'application réalisée dans ce tutoriel, vous devez écrire un test pour resluts.html, c'est-à-dire la classe ResultsView. Je sais que je ne peux pas. Cependant, vous pouvez voir que le contenu du test est presque le même que celui de la classe DetaiView que je viens d'écrire. La seule différence est que la partie polls: detail est remplacée par polls: results.

En revanche, le document officiel considère que la duplication est inhérente au test et présente les trois points suivants comme règles d'organisation.

--Split TestClass par modèle ou vue --Créez différentes méthodes de test pour chaque ensemble de conditions que vous souhaitez tester

Supplément

Je vous laisse avec les connaissances dont vous avez besoin pour exécuter ce didacticiel de test.

Copie de fichiers entre le conteneur et l'hôte dans Docker

・ Du conteneur à l'hôte

nom ou ID du conteneur docker cp: chemin vers le répertoire ou le fichier dans lequel vous souhaitez copier le répertoire de l'hôte vers lequel copier

・ De l'hôte au conteneur

docker cp chemin vers le répertoire ou le fichier que vous souhaitez copier Nom ou ID du conteneur: chemin à enregistrer dans le conteneur

docker exec -nom du conteneur/bin/bash

Entrez dans le conteneur avec etc.

py -c "import django; print(django.__path__)"

Vous devez donc rechercher le chemin du fichier source. Vous pouvez trouver l'emplacement du dossier Django en recherchant le chemin du fichier source, et si vous le copiez du côté hôte, vous pouvez le trouver du côté hôte dans le pire des cas. Bien sûr, si vous connaissez le répertoire jusqu'au fichier, vous pouvez simplement l'ajouter et copier uniquement ce fichier directement.

finalement

J'ai étudié le code de test pour la première fois cette fois, mais j'ai senti que c'était assez profond parce que je pouvais enfin m'appeler un codeur après avoir écrit ceci. La vue basée sur les classes était également déroutante, mais je ne pense pas que cela soit suffisant. Cependant, lorsque j'ai écrit le code de test de cette manière, j'ai reconfirmé le code que j'avais écrit jusqu'à présent, et j'ai senti que c'était important parce que j'ai approfondi à nouveau ma compréhension, y compris les parties qui n'étaient pas claires. En complément du didacticiel, il y a un chapitre qui se penche uniquement sur le test, donc j'aimerais le lire après la fin des chapitres 6 et 7 suivants.

référence

[Docker] Copier les fichiers entre le conteneur et l'hôte [[Django] Résumé des tests automatisés](https://qiita.com/okoppe8/items/eb7c3be5b9f6be244549#%E5%87%A6%E7%90%86%E3%81%AE%E6%B5%81% E3% 82% 8C) Création de la première application Django, partie 5

Recommended Posts

Historique d'apprentissage pour participer au développement d'applications d'équipe avec Python ~ Tutoriel Django 5 ~
Historique d'apprentissage pour participer au développement d'applications d'équipe avec Python ~ Tutoriel Django 4 ~
Historique d'apprentissage pour participer au développement d'applications d'équipe avec Python ~ Tutoriel Django 1, 2, 3 ~
Historique d'apprentissage pour participer au développement d'applications d'équipe avec Python ~ Tutoriel Django 6 ~
Historique d'apprentissage pour participer au développement d'applications d'équipe en Python ~ Tutoriel Django 7 ~
Historique d'apprentissage pour participer au développement d'applications d'équipe en Python ~ Page d'index ~
Historique d'apprentissage pour participer au développement d'applications d'équipe en Python ~ Pensez un peu à la définition des exigences ~
Historique d'apprentissage pour participer au développement d'applications d'équipe avec Python ~ Supplément d'éléments de base et construction de l'environnement jupyterLab ~
Apprentissage de l'historique pour participer au développement d'applications d'équipe avec Python ~ Créer un environnement Docker / Django / Nginx / MariaDB ~
Apprentissage de l'histoire pour participer au développement d'applications en équipe avec Python ~ Après avoir terminé "Introduction à Python 3" de paiza learning ~
Tutoriel Python Django (5)
Tutoriel Python Django (2)
Tutoriel Python Django (8)
Tutoriel Python Django (6)
Tutoriel Python Django (7)
Tutoriel Python Django (1)
Tutoriel du didacticiel Python Django
Tutoriel Python Django (3)
Tutoriel Python Django (4)
Tutoriel Boost.NumPy pour l'extension de Python en C ++ (pratique)
[Implémentation pour l'apprentissage] Implémentation de l'échantillonnage stratifié en Python (1)
Notes d'apprentissage pour la fonction migrations dans le framework Django (2)
Résumé du didacticiel Python Django
Créez un environnement interactif pour l'apprentissage automatique avec Python
Structure de répertoire pour le développement piloté par les tests à l'aide de pytest en python
Développement d'applications pour tweeter en Python à partir de Visual Studio 2017
Notes d'apprentissage pour la fonction migrations dans le framework Django (3)
Notes d'apprentissage pour la fonction migrations dans le framework Django (1)
Apprendre en profondeur à l'expérience avec Python Chapitre 2 (Matériel pour une conférence ronde)
Développement de framework avec Python
Environnement de développement en Python
Développement du kit SDK AWS pour Python (Boto3) dans Visual Studio 2017
Développement Slackbot en Python
Tutoriel pour faire du développement piloté par les tests (TDD) avec Flask-2 Decorators
Création d'un environnement de développement pour les applications Android - Création d'applications Android avec Python
Tutoriel pour faire du développement piloté par les tests (TDD) avec Flask-1 Test Client Edition
Flux d'apprentissage pour les débutants en Python
Plan d'apprentissage Python pour l'apprentissage de l'IA
Techniques de tri en Python
Développement Python avec Visual Studio 2017
Mise à jour automatique de l'application Qt pour Python
Fiche technique du didacticiel Python Django
La recommandation de Checkio pour apprendre Python
[Pour organiser] Environnement de développement Python
Développement Python avec Visual Studio
À propos de "for _ in range ():" de python
Que diriez-vous d'Anaconda pour créer un environnement d'apprentissage automatique avec Python?
Redimensionner automatiquement les captures d'écran de l'App Store pour chaque écran en Python
Analyse des ondes cérébrales avec Python: tutoriel Python MNE
Rechercher les fuites de mémoire dans Python
Rechercher des commandes externes avec python
8 commandes fréquemment utilisées dans Python Django
Matériel pédagogique Web pour apprendre Python
[Pour les débutants] Django -Construction d'environnement de développement-
Règles d'apprentissage Widrow-Hoff implémentées en Python
Options d'environnement de développement Python pour mai 2020
<Pour les débutants> bibliothèque python <Pour l'apprentissage automatique>
Paramètres Emacs pour l'environnement de développement Python
Python: prétraitement dans l'apprentissage automatique: présentation