PROJET AUTOBLOG


Sam & Max: Python, Django, Git et du cul

Site original : Sam & Max: Python, Django, Git et du cul

⇐ retour index

Quel ordi du coup ?

lundi 1 septembre 2014 à 09:17

Je vous ai saoulé avec le topic sur la nouvelle bécane que je devrais acheter, donc la moindre politesse c’est de vous communiquer le choix final.

En gros, beaucoup de gens sont allés dans le sens qu’un écran brillant, c’était pas la mer à boire, surtout avec les traitements anti-reflets actuels.

Du coup je me suis laissé tenté, et j’ai craqué pour l’ativ book 9 qui a tout ce que je veux, à part une écran mat.

La machine est fantastique : légère, rapide, un touchpad et un clavier excellents, et un très bon son. L’autonomie est loin de battre des records, particulièrement sous Linux, mais ce dernier s’installe facilement à condition de désactiver le secure boot.

En revanche, je hais l’écran brillant de toute mon âme.

On m’a menti. Non seulement le traitement anti-reflet marche queud’, mais en prime ce n’est pas que sur mon hamac qu’il me gène. Dès que je suis trop près d’une fenêtre, il faut éviter de travailler sur un fond trop sombre. C’est con, mon thême Sublime Text préféré est noir. Ça me fait pareil sur le mac de Max, putain de techno pourrie.

L’écran est tactile, ce qui ne sert absolument à rien pour mon usage. Et on a une très haute résolution de mes couilles à 12 milliards de pouces genre je copie Retina parce que c’est la mode. Résolution inutilisable sans un zoom pour lire le texte, et pour laquelle très peu d’applications sont adaptées. Je ne parle même pas des jeux.

Heureusement que la luminosité de l’écran est énorme sinon ça m’aurait dégoûté.

Bref, Du coup je down grade mon affichage. Qui aurait cru que je fasse ça une fois dans ma vie ? Et je vais acheter un filtre matifiant.

Le sperme, ça attache au fond

vendredi 29 août 2014 à 09:14

Sur le blog, on parle de S&M, mais peu de SM. La raison pour cela c’est qu’on est tout simplement pas des experts dans le domaine, et on reste dans la domination soft, bien que parfois on déborde un peu.

Malgré tout, en discutant avec des amis, je me suis aperçu que peu de gens ont vraiment goûté à la domi en dehors de videos. La raison est double.

D’abord, c’est n’est pas courant de tomber sur une personne qui soit vraiment capable de se trouver de l’autre côté de la laisse.

Ensuite, c’est socialement compliqué. Voyez-vous, on ne peut pas demander à quelqu’un si on peut le ou la soumettre, ça va exactement à l’encontre du principe de soumission qui, psychologiquement, n’admet pas la demande de permission. Et un ou une soumise va rarement demander à quelqu’un de le faire, car c’est facile de se griller dans un groupe. Parfois même, la personne ne sait pas qu’elle aime ça.

L’œuf, la poule, tout ça.

Voici donc un petit récit de ma dernière séance, pour que vous ayez une idée de ce que ça peut donner dans la vraie vie. Je répète encore une fois que malgré nos exactions, ceci n’est pas notre lot quotidien à Max et à Moi. On vous raconte les trucs qui valent le coup d’être publiés. C’est pas comme ça tous les jours à la maison, hein.

Lucie aime être soumise. Je le sais, par bouche à oreille. Un ami a eu une aventure avec elle, et sachant que j’avais couché avec la veille, il m’en a touché deux mots. Après s’être vu quelques fois, la miss est en confiance et je décide qu’il est temps de se faire une petite session.

Et ça, ça s’organise.

Certains aiment bien le faire au fil de l’eau, au gré de leur créativité. Moi j’aime bien scénariser, et donc préparer à l’avance, quitte à improviser si mon plan tombe à la flotte, ce qui arrive plus souvent que je le voudrais.

D’abord, choisir une date pour la faire venir. Pas besoin de prétexte, elle a envie de me voir, ce qui est déjà ça de moins à s’occuper. Mais il faut choisir un moment en journée où on a rien de prévu pour ne pas avoir à se presser, et surtout où il n’y aura personne à la maison. Je vous rappelle que je vis en colocation.

Ensuite, quelques accessoires. Une pote me fournit des attaches psychiatriques. Ce sont des sortes de ceintures conçues pour sangler les bras et les jambes des patients difficiles sur les lits d’hôpital. C’est fantastique car très rapide et facile à positionner (c’est à base d’une clé aimantée), très solide (on peut tirer dessus très fort) mais pensé pour ne pas blesser quelqu’un qui force dessus. C’est confortable, assez joli, et suffisamment angoissant.

Je les attache sur le devant de ma mezzanine, j’ai décidé qu’elle sera debout. La domination, c’est une suite de décisions, puisque c’est une prise de contrôle.

Puis je découpe un bout de scotch gris que je mets à portée de main, pour la bâillonner. Elle est chanteuse, et les ball gags ne fonctionnent absolument pas pour la mettre en sourdine.

Pour finir, il lui faut de quoi indiquer qu’elle atteint sa limite sans pouvoir prononcer un safe word puisqu’elle ne pourra pas parler. Je prends donc un maracasse.

Ah, oui, je rajoute un couteau de chasse bien aiguisé avec lequel je planifie de découper ses vêtements, et donc un budget pour lui racheter les dits apparats après. Qui casse paie.

Il n’y a plus qu’à la faire venir, et à attendre.

Je déteste l’attente dans ces moments là. Max est tout le contraire, ça l’excite. Même quand il va ramener des putes de bar, il passe la nuit là-bas car il aime prendre son temps. Moi ça me stresse. J’ai le cœur qui bat à 100 à l’heure, il y a tellement de choses qui peuvent foirer. Et foirer grave. Le scénar qui ne colle pas et on a l’air ridicule, le scénar qui colle trop et la meuf qui me prend pour un psychopathe, les voisins qui appellent les flics, le couteau qui glisse et qui la blesse… Il faut pas faire ça quand on a la poisse.

Finalement elle arrive à l’heure dite (Dieu, que j’aime les femmes ponctuelles !), en tenue légère, ce qui m’arrange bien. On s’embrasse tendrement, et je fais avec elle quelques pas de valse pour la rapprocher des attaches.

On se chauffe un peu, vêtement par vêtement, et je tâte le terrain. Avec un petit sourire, je lui tends un bras, et l’attache, le regard coquin. Elle se laisse faire. C’est bon signe, j’attache donc le second, et lui glisse le maracasse dans une main. Je lui explique les règles du jeu : “Secoue et j’arrête sur le coup, lâche, et je te détache”. J’attends quelques secondes sans rien dire pour voir si elle a bien compris. Elle ne panique pas. Bien. Mais ça va changer, car on passe à la mise en scène.

Changement de rythme.

Je fais passer sans transition mon visage d’un sourire doux à un air dur, et je la bâillonne sans crier gare.

“T’es vraiment conne Lucie. Tu connais un mec depuis, quoi, une semaine ? Et tu le laisses te faire venir dans une maison vide et t’attacher à un lit sans te poser de question ? C’est naïf.”

Je verrouille ma porte, et baisse le store, heureusement électrique, de ma fenêtre. Je prends mon temps.

“Personne ne pourra t’entendre. Et maintenant personne ne pourra te voir.”

Je la sens mal à l’aise, en train de se demander si je joue la comédie ou si elle est tombée sur un serial killer.

Je sors le couteau de chasse, et plongeant mes yeux dans les siens, je peux voir que le doute commence à l’angoisser. Mais elle a le maracasse, et elle n’essaye pas de s’en servir. Elle m’expliquera après qu’au début elle avait complètement oublié qu’elle l’avait dans la main.

Je lui enlève ce qui reste de ses vêtements, en jouant avec la lame sur son corps. Si vous faites ça, faites très attention, l’accident est vraiment vite arrivé. Je suis très concentré pendant toute la procédure, entre le jeu d’acteur et mon désir d’éviter de lui trancher la jugulaire.

Il me reste le soutif et je n’ai encore rien coupé, alors je le fends en deux. Elle se débat un peu, je lui mets une baffe et un rappel oral à l’ordre. Elle se calme, et mouille instantanément. Je peux sentir la cyprine alors que je suis à hauteur de son cou. Pas de chic-chic. C’est gagné, je ne finirai pas en garde à vue ce soir.

J’avais prévu de faire durer le truc plus longtemps, mais vu que la demoiselle est prête, et que j’ai vraiment très, très envie de la baiser, j’accélère le programme. Je pousse un fauteuil, et la colle dessus de manière très inconfortable. À ce stade je ne donne plus d’ordre, je la prends par les cheveux et la fais bouger au gré de mes envies. Et elle obéit.

Elle a les genoux sur le siège, le ventre sur le dossier, la tête dans le vide et les bras tendus, toujours attachés en l’air. Je tire l’assise pour allonger le vide entre sa position et la mezza, elle doit forcer un peu pour garder sa posture.

Je la prends sans plus attendre, et sans ménagement. Je la pénètre presque violemment, mais elle est plus excitée que si je l’avais léchée pendant 20 minutes, et ça rentre tout seul. Elle ne m’a pas vu mettre la capote, je me demande si elle se demande.

Je la tire en arrière par sa belle tignasse dorée, je la pousse en avant pour qu’elle se mange le canapé, je bouge ses jambes, les lève, les baisse. Je la fesse, évidement. Puis je lui colle un doigt dans le cul.

Premier et unique son de maracasse. La demoiselle n’aime pas l’anal. Soit. Je retire mon auriculaire.

Le respect de son souhait malheureusement, inverse la balance de la relation. Il faut donc que je la soumette à nouveau. Je ne l’enculerai pas, mais je la réprimande, et je vais la punir.

Je la mets en équilibre sur le dossier du fauteuil, les jambes écartées, elle est debout à un mètre du sol, mais toujours attachée. J’ai une idée.

Je vais chercher une tondeuse à barbe, et je tonds la petite touffe de poils qui lui reste au niveau de la chatte, puis je lui cale l’engin encore vibrant dans le vagin, les dents à l’extérieur, bien entendu.

“Si tu le fais tomber, tu en prends une.”

Et je vais me faire un thé. Je prends mon temps, faire bouillir l’eau, choisir sa marque, mettre ça dans une jolie théière.

Je ramène tout ça, et je laisse infuser. Pendant que les feuilles délivrent leurs senteurs, je monte sur le lit, délivre sa bouche pour lui donner immédiatement de quoi s’occuper. Elle me suce avec beaucoup d’ardeur, et elle est assez douée, bien que moins qu’elle ne le croit. Elle s’en était vanté.

Elle arrête, avec un regard de défi, et reprend une claque sans plus attendre. Elle y remet deux fois plus de cœur, j’entends sa gorge glousser, la tondeuse vibrer, et j’ai envie de venir.

Mais pas comme ça.

Je la rabaisse, lui disant qu’elle s’y prend tellement mal que j’allais devoir faire le boulot moi-même. Je me masturbe au dessus d’elle, et finis sur son visage.

Je retourne vers elle, et lui annonce qu’elle a été sage, et a le droit de se faire libérer un bras. Lequel choisit-elle ? Le droit ? Je détache le gauche. Puis je m’en vais boire mon thé. Lentement. Gorgée par gorgée.

Je retourne à mon mouton, ses petits yeux bleus sont ravissants au milieu de son visage souillé qu’elle a maladroitement tenté d’essuyer avec son bras valide. C’est une victime idéale, la peau claire, l’air innocent… Je lui détache l’autre main et lui passe la sangle autour du cou, la promenant ainsi jusqu’à mon ordinateur, où j’entreprends de checker mes mails avec une seconde tasse de thé.

Elle ne dit rien, je ne veux pas qu’elle s’ennuie, et lui commande de me lécher les pieds, n’ayant pas du tout envie de me faire pomper à l’instant. C’est très agréable, de se faire lécher les pieds.

Elle commence à avoir la langue sèche, alors je lui dépose une tasse et l’autorise à laper. Et je continue ma lecture du shaarli de Sebsauvage, par flux RSS car son design m’arrache les yeux.

Finalement, je la détache, je l’embrasse amoureusement. C’est terminé. Je la câline. La rassure. Cette phase là est importante. Elle réinstaure le respect. Sépare le jeu de la réalité. Remet les pendules à l’heure, les points sur les G…

Elle me confie ses peurs, elle tremble un peu. Elle a adoré. Je suis soulagé. J’avais peur moi aussi, tellement peur que ça rate. Mais je ne lui ai certainement pas dit ça. A la place, je lui propose d’aller faire Pretty Woman pour remplacer son haut lacéré.

Pendant qu’elle était dans mes bras, j’ai réfléchi. Réfléchi à la prochaine fois. Pas pour tout de suite. C’est vraiment trop de boulot.

De retour, et déjà dans la merde :)

jeudi 28 août 2014 à 09:49

Bon, le blog repart, et pour bien commencer, on a une partition corrompue. Ce qui explique les hauts et les bas du blog de ces derniers jours : pas d’images, categs / tags vides, lenteurs, etc.

En fait, c’est le ext4 de /tmp qui a été monté en read-only par le système pour éviter de tout pêter. Mais du coup, PHP et MailleScule n’aiment pas trop.

La solution temporaire a été de redem les deux process et bidouiller leur config pour que leurs fichiers temp tapent dans une partition saine, le temps de resoudre le problème.

Comme l’installation est bidouillée de chez bidouillée, on a vraiment pas envie de tout réinstaller, donc on fait de l’acharnement thérapeutique pour sauver ce serveur.

Bref, vive l’auto-hébergement !

Un wifi bien de chez nous

mercredi 27 août 2014 à 12:46

La gare de Lyon est ma gare de Paris préférée. Celle de l’Est est vieillotte, Montparnasse est difficile d’accès, et la gare du Nord est un labyrinthe conçu pour s’assurer que vous ratiez votre train à 20 secondes près.

À la gare de Lyon, c’est lumineux, bien indiqué, sur la ligne A du RER. Et au guichet, une charmante dame avec la quarantaine bien portée me sourit en concluant :

«On a le wifi dans le hall depuis cet été, monsieur »

C’est sur cette remarque pleine d’entrain que je me suis assis dans un des sièges bordeaux encore libres. Diantre, des places assises et le Wifi ? La SNCF a-t-elle enfin demandé à un de ses cadres d’utiliser son propre service afin qu’il réalise que les gens qui patientent préfèrent ne pas le faire debout, et connectés ? Comble du bonheur, il y a des prises électriques, tenez-vous bien, à la hauteur des accoudoirs.

Vraiment, tout cela est suspect.

C’est que je m’étais préparé psychologiquement moi. Une inconfortable attente, vide de sens et bien remplie d’ennui. Je suis troublé, j’ai du mal a accepter ma bonne fortune.

Je clique donc sur mon petit icône en forme de part de pizza, et c’est presque bien, j’ai seulement besoin de choisir entre :

Catpure d'écran de la liste des spots wifi

Ce qui est marrant, c'est que la liste change régulièrement

Pour vous retranscrire l’expérience au maximum, il faut que je vous fasse partager l’ambiance sonore pendant ma quequette du Graal.

D’abord il y a le haut parleur qui me rappelle toutes les 5 minutes que mon train a du retard, mais que ça va, il est en préparation.

Ensuite il y a le piano gare. Une initiative que j’ai toujours trouvé charmante jusqu’à ce qu’un amateur acharné décide de massacrer Yann Tiersen pendant l’intégralité de mon interminable temps d’attente.

Et bien entendu, alors que j’écris cette phrase, un môme a décidé de chialer derrière moi, en stéréo pour ne pas en rater une miette.

Mais retournons à nos wifis.

Je fais une tentative – quel naïf – sur notre SSID qui mélange mauvais goût capital et semi-1337-speak : LE_PREM1ER_Wifi_Gratuit. Je suis accueilli par ceci :

Capture d'écran d'un formulaire désuet

Si vous ne trouvez pas votre PDA, cherchez là où vous avez rangé votre minidisc

Évidement, si on valide le formulaire, il ne se passe absolument rien.

C’est pas grave, je passe à Relay_Wifi_Gratuit. Mais là, rien ne s’affiche, même pas de portail.

Bon, je tente *SNCF gare-gratuit, en me demandant si la petite étoile suppose qu’il y a une condition d’achat écrite en tout petit dans un des paquets ARP. Alors, là, on passe tout de suite au niveau sup.

On commence par un portail bien design avec une petit animation dans l’air du temps qu’on vous force à regarder :

Catpure d'écran d'un portail avec video

Les videos d'intro sont les musiques d'ascenseur de notre décénie

Et ensuite on a le choix entre la pilule rouge, qui ne marche pas, et la pilule jaune, qui vous redirige vers un formulaire magique :

Catpure d'écran du choix du portail wifi

Souvient toi Néo, je t'offre la vérité. Rien de plus. Et elle est toute pérave.

Je dis magique car ça faisait longtemps que je n’avais pas ressenti tant d’émerveillement à la validation d’une balise <form>. Ça avait un petit goût de Disney :

Catpure d'écran d'une alerte javascript suite à la validation d'un formulaire

Je vous avais parlé des formulaires REST. Voici un formulaire "va t'en".

Bon, là, réflexe habituel, je reload. Peut être un problème de cache ou de réseau. Je me retape la vidéo et le formulaire., je désactive Adblock, au cas où. Je reload, je me retape la putain de video. Rien. Je commence avec ghostery, je me tape cette vidéo de merde pour la 4eme fois. Il ne reste plus qu’à tester, un autre navigateur. J’ai un petit chrome vierge de toute extension près pour ça, mais là, je suis carrément pris de cours :

Capture d'écran du portail sous chrome : une page vide sans https

Pourquoi sécuriser une page vierge, en même temps ?

Notez la redirection sur une autre URL, et non sécurisée en plus, qui finit en page blanche.

Cocorico :

A tout hasard, je retente _Wifi Metropolis. Maintenant que je sais qu’ils font du sniffing, le fait d’avoir deux fois le même portail sous des alias me fais dire que ça peut marcher. Et bingo, ici la pilule rouge marche et me donne 20 minutes d’internet illimité gratuit.

La seule bonne surprise, c’est que quand ils disent internet, c’est bien de l’internet, et pas du Web bridé. J’ai accès à SSH et IRC, je pip sans blocage de proxy, et tout est très fluide. Mais bon entre la rédaction de l’article, les tentatives de connections et la constante interruption pour écouter les annonces afin de ne pas rater mon train, je dois partir.

Snif.

Ce faisant, j’ai croisé un dispositif d’étude satisfaction client qui frise le foutage de gueule :

Photo du dispositif de mesure de satisfaction client

C'est comme les réductions pour lesquelles il faut envoyer une lettre pour obtenir le remboursement après achat...

A gauche, un bouton pour dire en une seconde que tout va bien. A droite, un QR code pour dire que vous connecter à un site pour exprimez ce que n’aimez pas. Je crois que je comprends pourquoi le wifi est si dur à avoir :)

Bon, ça concerne l’art en gare, donc pas de défouloir possible pour moi.

Enfin, je râle, mais contrairement à beaucoup de français, j’aime bien la SNCF. Après avoir voyagé dans bien des pays, où le train est cher, inconfortable, peu accueillant et avec des horaires auxquelles on ne peut accorder aucune confiance (et ne croyez pas que je parles du tiers-monde, essayez les UK…), je suis même assez épaté.

Ils gèrent un flux de voyageurs gigantesque et pas toujours très malins, des crises tous les jours, plutôt bien d’ailleurs, dans l’intégralité de la France, y compris les petits bourgades de campagne de mon enfance. On est souvent bien accueilli. Les trains sont confortables. Les tarifs sont raisonnables. Et croyez moi quand je vous dis que jusqu’à 4h de retard, on est dans la tranche haute de la fiabilité sur les grandes lignes partout dans le monde. Sauf au Japon, mais les androïdes ne comptent pas.

Même si mon train était en retard. Même si ils se sont gourés dans la numérotation des voitures. Même si ils m’ont mis à côté d’un malpoli qui écoute sa musique à fond et me fait profiter de chacune des basses de son morceau sans goût ni originalité. Même si ils ne sont pas foutu de faire en Wifi ce que MacDo fait depuis plus de 10 ans.

Hey, je peux être à Lyon en 3h. Et l’uniforme gris de la contrôleuse lui fait un super cul.

Je vous laisse, mon train est arrêté en pleine voie, c’est palpitant.

Un gros guide bien gras sur les tests unitaires en Python, partie 3

mardi 26 août 2014 à 11:24

Pas mal de temps s’est écoulé depuis notre dernier article sur les tests. Ok, le dernier article Python tout court puisque je vous ai lâchement abandonnés pendant plus d’un mois. Je vous rassure, je n’ai pas du tout pensé à vous, je me suis bien amusé.

Mais je ne vous avais pas non plus oublié. J’étais juste parfaitement fainéant. C’est que ça demande du taff ces petites bêtes là.

Aujourd’hui, nous allons voir la même chose que la partie précédente, mais avec une autre lib.

En effet, si vous voulez rester sains d’esprit et ne pas perdre votre motivation à rédiger des tests, utiliser le module unittest est une mauvaise idée. C’est verbeux, lourd, pas pratique. C’est caca.

Il existe bien mieux, et toutes les personnes que je connais qui sont sérieuses à propos des tests l’utilisent : PyTest.

Et pour donner un petit goût de fiesta :

Principe

Je vous en avais parlé ici, principe de pytest, c’est :

Ça s’installe avec pip :

pip install pytest

Et en gros, au lieu de faire :

import unittest

class TestBidule(unittest.TestCase):

    def test_machin(self):
        self.assertEqual(foo, bar)

if __name__ == '__main__':
    unittest.main()

On fait:

def test_machin():
    assert foo == bar

Yep, c’est tout. Même pas d’import. C’est beau non ?

Il y a beaucoup de magie pour que ça marche. D’abord, le lanceur de pytest détecte toutes les fonctions nommées test_* contenues dans des modules nommés également avec ce motif, et les lance comme un test. Ensuite, il analyse les assert, et devine ce que vous voulez faire avec, et fait le bon test qui va bien.

Ce genre d’opération est un des rares endroits où je tolère de la grosse magie en Python. En effet, les tests, c’est tellement relou que si on n’a pas un moyen ultra simple de les faire, on ne les fait pas.

Traduction

On va donc prendre les exemples qu’on a vus avec unittest, et les traduire dans leur équivalent pytest.

import unittest

from mon_module import get

class TestFonctionGet(unittest.TestCase):

    def test_get_element(self):
        simple_comme_bonjour = ('pomme', 'banane')
        element = get(simple_comme_bonjour, 0)
        self.assertEqual(element, 'pomme')

    # Il faut choisir un nom explicite pour chaque méthode de test
    # car ça aide à débugger.
    def test_element_manquant(self):
        simple_comme_bonjour = ('pomme', 'banane')
        element = get(simple_comme_bonjour, 1000, 'Je laisse la main')
        self.assertEqual(element, 'Je laisse la main')


if __name__ == '__main__':
    unittest.main()

Devient alors :

from mon_module import get

def test_get():
    simple_comme_bonjour = ('pomme', 'banane')
    element = get(simple_comme_bonjour, 0)
    assert element == 'pomme'

def test_element_manquant():
    simple_comme_bonjour = ('pomme', 'banane')
    element = get(simple_comme_bonjour, 1000, 'Je laisse la main')
    assert element == 'Je laisse la main'

On lance la commande py.test (le point est important), sans spécifier de fichier :

$ py.test .
============================= test session starts ==============================
platform linux2 -- Python 2.7.6 -- py-1.4.23 -- pytest-2.6.1
collected 2 items

test_get.py ..

Arf, j’ai lancé Python 2.7 au lieu du 3.4. Les vieilles habitudes ont la vie dure. Pas grave, c’est pareil avec les deux versions de Python.

Dans tous les cas, pytest va parcourir le dossier donné récursivement, et détecter tous les modules Python nommés test_ puis extraire les tests qu’il contient. L’effort à fournir est minimal, et c’est ce qu’on lui demande.

Les erreurs

Vu qu’on n’utilise pas de méthode assertChose, on pourrait croire que les informations qu’on obtient en retour sont limitées. Que nenni, pytest fait beaucoup d’efforts pour extrapoler du sens depuis nos assert et va nous pondre un rapport tout à fait complet.

Prenons le cas :

class TestFonctionGet(unittest.TestCase):

    def test_get_element(self):
        simple_comme_bonjour = ('pomme', 'banane')
        element = get(simple_comme_bonjour, 0)
        self.assertEqual(element, 'pomme')

    def test_element_manquant(self):
        simple_comme_bonjour = ('pomme', 'banane')
        element = get(simple_comme_bonjour, 1000, 'Je laisse la main')
        self.assertEqual(element, 'Je laisse la main')

    def test_avec_error(self):
        simple_comme_bonjour = ('pomme', 'banane')
        simple_comme_bonjour[1000]

Qui donnait :

$ python test_get.py
E..
======================================================================
ERROR: test_avec_error (__main__.TestFonctionGet)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_get.py", line 40, in test_avec_error
    simple_comme_bonjour[1000]
IndexError: tuple index out of range

----------------------------------------------------------------------
Ran 3 tests in 0.001s

FAILED (errors=1)

Avec pytest, le code est allégé :

def test_get():
    simple_comme_bonjour = ('pomme', 'banane')
    element = get(simple_comme_bonjour, 0)
    assert element == 'pomme'

def test_element_manquant():
    simple_comme_bonjour = ('pomme', 'banane')
    element = get(simple_comme_bonjour, 1000, 'Je laisse la main')
    assert element == 'Je laisse la main'

def test_avec_error():
    simple_comme_bonjour = ('pomme', 'banane')
    simple_comme_bonjour[1000]

Et la sortie est pourtant un peu plus lisible :

$ py.test
============================= test session starts ==============================
platform linux2 -- Python 2.7.6 -- py-1.4.23 -- pytest-2.6.1
collected 3 items

test_get.py ..F

=================================== FAILURES ===================================
_______________________________ test_avec_error ________________________________

    def test_avec_error():
        simple_comme_bonjour = ('pomme', 'banane')
>       simple_comme_bonjour[1000]
E       IndexError: tuple index out of range

test_get.py:22: IndexError

Le plus intéressant est la manière dont sont gérées les erreurs logiques. Encore une fois l’exemple précédent :

class TestFonctionGet(unittest.TestCase):

    def test_get_element(self):
        simple_comme_bonjour = ('pomme', 'banane')
        element = get(simple_comme_bonjour, 0)
        self.assertEqual(element, 'pomme')

    def test_element_manquant(self):
        simple_comme_bonjour = ('pomme', 'banane')
        element = get(simple_comme_bonjour, 1000, 'Je laisse la main')
        self.assertEqual(element, 'Je laisse la main')

    def test_avec_echec(self):
        simple_comme_bonjour = ('pomme', 'banane')
        element = get(simple_comme_bonjour, 1000, 'Je laisse la main')
        self.assertEqual(element, 'Je tres clair, Luc')

Et :

$ python test_get.py
F..
======================================================================
FAIL: test_avec_echec (__main__.TestFonctionGet)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_get.py", line 45, in test_avec_echec
    self.assertEqual(element, 'Je tres clair, Luc')
AssertionError: 'Je laisse la main' != 'Je tres clair, Luc'
- Je laisse la main
+ Je tres clair, Luc


----------------------------------------------------------------------
Ran 3 tests in 0.002s

FAILED (failures=1)

Peut-on faire mieux ? Of course :

def test_get():
    simple_comme_bonjour = ('pomme', 'banane')
    element = get(simple_comme_bonjour, 0)
    assert element == 'pomme'

def test_element_manquant():
    simple_comme_bonjour = ('pomme', 'banane')
    element = get(simple_comme_bonjour, 1000, 'Je laisse la main')
    assert element == 'Je laisse la main'


def test_avec_echec():
    simple_comme_bonjour = ('pomme', 'banane')
    element = get(simple_comme_bonjour, 1000, 'Je laisse la main')
    assert element == 'Je tres clair, Luc'

Et malgré cette concision, pytest est très prolixe dans sa sortie :

$ py.test
============================= test session starts ==============================
platform linux2 -- Python 2.7.6 -- py-1.4.23 -- pytest-2.6.1
collected 3 items

test_get.py ..F

=================================== FAILURES ===================================
_______________________________ test_avec_echec ________________________________

    def test_avec_echec():
        simple_comme_bonjour = ('pomme', 'banane')
        element = get(simple_comme_bonjour, 1000, 'Je laisse la main')
>       assert element == 'Je tres clair, Luc'
E       assert 'Je laisse la main' == 'Je tres clair, Luc'
E         - Je laisse la main
E         + Je tres clair, Luc

test_get.py:24: AssertionError

Je m’arrête pour faire une pause “vis ma vie de Sam”. Je suis en train de rédiger cet article en face d’une caricature de hipster (j’écris beaucoup dans les transports) : la barbe de baroudeur en brousse, la coupe de cheveux de “Thrift shop” absolument immaculée, les lunettes de mamie, le Mac book tout neuf, le petit t-shirt discret mais branché, tout y est. Sauf que là, il vient de sortir un appareil à pellicule pour prendre une photo du paysage, et j’ai beaucoup de mal à me retenir de rire. Ce paragraphe me permet de maintenir une apparence civile. Putain je suis sûr que c’est du noir et blanc et qu’il les développe lui-même.

Fin de la parenthèse.

Contrairement à unittest, pytest n’a pas besoin d’une floppée de méthodes assert*, et il comprend parfaitement les idiomes Python :

assertDictEqual => assert a == b
assertFalse => assert not a
assertGreater => assert a > b
assertIn => assert a in b
assertIs => assert a is b

Etc.

Setup et TearDown à la demande

Pytest ne possède pas de méthode setup() et teardown(). A la place, il y a un mécanisme dit “de fixture”.

Il s’agit de marquer une fonction avec un décorateur. Ensuite, si vous la déclarez en paramètre d’un test, pytest va automatiquement l’appeler au lancement de ce test. C’est une forme d’injection de dépendance, un peu à la angularjs.

C’est pas clair, hein ? Je sens que c’est pas clair.

Mais les exemples sont là pour ça :

import pytest # cette fois il faut un import

# Je déclare une fixture, qui peut (ce n'est pas obligatoire), retourner
# quelque chose
@pytest.fixture()
def simple_comme_bonjour():
    return ('pomme', 'banane')

# Pour chaque test où je déclare le nom de la fixture en paramètre, pytest
# va appeler la fonction juste avant le test et passer son résultat
# (fut-il None), en argument de ce test
def test_get(simple_comme_bonjour):
    element = get(simple_comme_bonjour, 0)
    assert element == 'pomme'

def test_element_manquant(simple_comme_bonjour):
    element = get(simple_comme_bonjour, 1000, 'Je laisse la main')
    assert element == 'Je laisse la main'

L’avantage du système de fixtures, c’est qu’on n’est pas obligé d’exécuter la fixture pour tous les tests, seulement ceux pour lesquels on en a besoin. On peut combiner plusieurs fixtures de manière très souple, juste en déclarant plusieurs paramètres, sans avoir à faire des classes dans tous les sens. En fait, les fixtures peuvent avoir des fixtures en paramètre, histoire de faire des chaînes de dépendance.

Ici, il n’y a que l’exemple du setup, mais pas du tear down. Pour cela, on peut utiliser un autre type de fixture, qui demande l’utilisation d’un générateur :

import pytest


# On passe de pytest.fixture() a pytest.yield_fixture()
@pytest.yield_fixture()
def simple_comme_bonjour():
    # tout ce qui est setup() va au dessus du yield. Ca peut etre vide.
    print('Avant !')

    # Ce qu'on yield sera le contenu du parametre. Ca peut etre None.
    yield ('pomme', 'banane')

    # Ce qu'il y a apres le yield est l'equivalent du tear down et peut être
    # vide aussi
    print('Apres !')

def test_get(simple_comme_bonjour):
    element = get(simple_comme_bonjour, 0)
    assert element == 'pomme'

def test_element_manquant(simple_comme_bonjour):
    element = get(simple_comme_bonjour, 1000, 'Je laisse la main')
    assert element == 'Je laisse la main'

def test_avec_echec(simple_comme_bonjour):
    element = get(simple_comme_bonjour, 1000, 'Je laisse la main')
    assert element == 'Je tres clair, Luc'

Comme pour avec unittest, le mot “Apres !” apparait bien malgré l’échec du 3eme test, on peut donc sans problème mettre des opérations de nettoyage dedans :

$ py.test -s
============================= test session starts ==============================
platform linux2 -- Python 2.7.6 -- py-1.4.23 -- pytest-2.6.1
collected 3 items

test_get.py Avant !
.Apres !
Avant !
.Apres !
Avant !
FApres !


=================================== FAILURES ===================================
_______________________________ test_avec_echec ________________________________

simple_comme_bonjour = ('pomme', 'banane')

    def test_avec_echec(simple_comme_bonjour):
        element = get(simple_comme_bonjour, 1000, 'Je laisse la main')
>       assert element == 'Je tres clair, Luc'
E       assert 'Je laisse la main' == 'Je tres clair, Luc'
E         - Je laisse la main
E         + Je tres clair, Luc

test_get.py:33: AssertionError
====================== 1 failed,

Notez que j’utilise l’option -s, qui demande à pytest de ne pas capturer la sortie de mon programme. Sinon je ne verrai pas mes prints.

Contrairement aux setup et tear down, on n’est pas obligé d’utiliser une fixture pour un test donné, il suffit de ne pas l’ajouter en paramètre, et ça ne sera pas lancé pour ce test là. Mais parfois, on veut qu’une fixture soit lancée partout et on se fiche de la valeur de retour. Dans ce cas, on peut utiliser @pytest.fixture(autouse=True).

Outils

Pytest possède beaucoup d’extensions tierces parties qui fournissent des fixtures. Par exemple, pytest-django fournit des fixtures pour le client HTTP de test, l’override temporaire des settings et le reset de la base de données. La lib elle-même embarque quelques fixtures pratiques, dont :

[LOL, mon hispter vient de se passer la main dans les cheveux, avec une emphase consciencieuse, comme un chat fait sa toilette. Je m'attends à ce qu'il se lèche les poils d'ici quelques minutes.]

Plus que cela, pytest vient également avec une pléthore d’options, et je ne saurais trop vous conseiller de lire l’output de py.test --help afin de faire le tour de ce qui s’offre à vous. Quelques exemples :

Par ailleurs, pytest est très sociable et s’entend bien avec tous les outils de tests existants. Il va détecter les tests unittest, il prend en compte les fichiers tox.ini, et il existe même un plugin nose intégré.

D’ailleurs, j’utilise souvent un fichier tox.ini à la racine de mes tests contenant ceci :

[pytest]
addopts = --ignore="virtualenv" -s

Cela ajoute automatiquement ces options à la commande py.test, puisque je les utilise tout le temps. Ça m’évite de les taper.

400 lignes pour dire, n’utilisez pas le module unittest, utilisez pytest.

Dans la prochaine partie, je me chargerai de faire le point sur les doctests, puis nous glisserons sur des sujets plus philosophiques comme “quand tester”, “quoi tester”, “comment tester”, “que faire si je suis testé positif”, etc.

Si le dieu de la procrastination le veut, on fera même un petit tour par les mocks, les outils d’intégration continue et le test end2end. C’est pas forcément du test unitaire, mais c’est du test, et je vais pas renommer mon dossier maintenant que ces belles URLs sont référencées pour Google.