PROJET AUTOBLOG


Sam et Max

source: Sam et Max

⇐ retour index

Wrap in Try / Except dans Sublime Text

jeudi 20 février 2014 à 23:57

Très souvent il m’arrive de vouloir mettre un bout de mon code dans un blog try / except, et il faut couper le bloc de code, ecrire le try/except, coller le code, puis re-indenter au besoin.

C’est con, on a un éditeur pour faire le taff pour nous et d’ailleurs, PyDev le fait très bien. Soyons fainéant.

On va se rajouter une petit snippet qui permet de faire ça automatiquement. Il faut ouvrir le menu Tools > new snippet et un peu de xeumeuleu dedans :

<snippet>
<content><![CDATA[
${SELECTION/^( *)([^ ].*)/$1/g}try:
${SELECTION/(^ *|\n *)/$1    /g}
${SELECTION/^( *)([^ ].*)/$1/g}except ${1:Exception as e}:
    ${SELECTION/^( *)([^ ].*)/$1/g}${2:import ipdb; ipdb.set_trace()}
]]></content>
    <scope>source.python</scope>
    <description>Wrap in Try/Except</description>
</snippet>

Ca récupère la selection, place le try/except, replace la sélection et reindente grâce à une regex. Et seulement si le fichier est du code Python. Joie.

On sauvegarde ça sous un nom clair genre “wrap_in_try_except.sublime-snippet”.

Puis on fait un petit tour dans Préférence > Key Bindings – User et on se rajoute un raccourci clavier pour profiter de notre nouveau joujou :

      { "keys": ["ctrl+shift+y"], "command": "insert_snippet", "args": {"name": "Packages/User/wrap_in_try_except.sublime-snippet"} }

Et voilà :

ligne_qui_pouet

Devient, après selection et un petit ctrl + shift + y :

try:
    ligne_qui_pouet
except Exception as e:
    import ipdb; ipdb.set_trace()

On peut tabuler entre la définition de l’exception à catcher et le contenu du block except pour les changer au besoin rapidement.

Par contre, ça ne marche que si on indente avec des espaces. Mais ceux qui utilisent des tabs pour indenter ne méritent pas qu’on les aide de toute façon (si c’est votre cas, quittez ce site, tout de suite, houste !).

flattr this!

Les bases de Numpy

mercredi 19 février 2014 à 20:18

Numpy est une lib destinée à la manipulation de grands ensembles de nombres et est très utilisée par la communauté scientifique.

Elle propose des types et des opérations beaucoup plus performants que ceux de la lib standard, et possède des raccourcis pour les traitements de masse.

Malheureusement, c’est aussi une lib complexe, et, comme souvent dans le monde de la science, les tutos pourraient être plus clairs.

Cet article ne prétend pas à une couverture exhaustive de Numpy, d’autant que je n’ai pas le niveau en maths pour faire une simple dérivée alors des opérations matricielles complexes…

Mais ça devrait permettre de démystifier le truc pour les gens qui regardent ça de loin comme si c’était un pingouin au Mali.

Commencez par installer la bestiole avec un pip install numpy. Faites-vous un café pendant que ça compile.

array sur image

A la base de Numpy, il y a la manipulation d’ensembles ordonnés de nombres. On peut faire les opérations voulues sur un type list ordinaire, mais ce serait lent, et ça prendrait pas mal de mémoire.

L’alternative est d’utiliser un type optimisé comme ndarray, fourni par Numpy.

Cela se manipule comme une tuple, avec une différence majeure : il ne peut contenir qu’un seul type de données. Donc on ne met que des int, ou que des str, que des bool, etc.

On peut construire un array à partir de n’importe quel itérable :

>>> from numpy import array
>>> array([1, 2, 3])
array([1, 2, 3])
>>> array(u"azerty")
array(u'azerty',
      dtype='<U6')
>>> array((1.0, 2.0, 3.0))
array([ 1.,  2.,  3.])

Mais souvent on utilisera une fonction générant un array automatiquement afin d’éviter de créer deux structures de données (la liste, puis l’array par exemple).

On peut utiliser arange, qui est l’équivalent de range, mais pour les arrays :

>>> from numpy import arange
>>> arange(1, 100, 2)
array([ 1,  3,  5,  7,  9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33,
       35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63, 65, 67,
       69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 95, 97, 99])

Ainsi que des choses plus perfectionnées comme linspace, qui retourne un array de n nombre de valeurs uniformément réparties entre deux bornes:

>>> from numpy import linspace
>>> linspace(0, 100, 15) # 15 valeur entre 0 et 100
array([   0.        ,    7.14285714,   14.28571429,   21.42857143,
         28.57142857,   35.71428571,   42.85714286,   50.        ,
         57.14285714,   64.28571429,   71.42857143,   78.57142857,
         85.71428571,   92.85714286,  100.        ])

Comme les tuples, les arrays sont itérables, sliceables, indexables et de taille fixe :

>>> sistance = arange(10)
>>> for x in a: # iterable
...     print x
...
0
1
2
3
4
5
6
7
8
9
>>> sistance[2:4] # sliceable
array([2, 3])
>>> sistance[-1] # indexable
9
>>> sistance.append(11) # taille fixe
Traceback (most recent call last):
  File "<ipython-input-17-389b8ea2fe68>", line 1, in <module>
    sistance.append(11)
AttributeError: 'numpy.ndarray' object has no attribute 'append'

L’array représente donc une photographie figée de vos données, mais comme vous allez le voir, rapide et précise à manipuler.

Opérations groupées

La caractéristique marquante de l’array, c’est que si vous lui appliquez un opérateur mathématique, un nouvel array est retourné dont TOUTES les valeurs ont été modifiées.

Par exemple, si vous multipliez un array, un nouvel array est retourné avec toutes les valeurs multipliées :

>>> duku = array([1, 2, 3])
>>> au_milieu = duku * 2
>>> au_milieu
array([2, 4, 6])

En fait, numpy fait une boucle implicite – et performante – sur tout l’array pour chaque opération mathématique. Et ça devient intéressant quand on veut faire des opérations entre plusieurs arrays entre eux :

>>> duku
array([1, 2, 3])
>>> au_milieu
array([2, 4, 6])
>>> de_tram = duku + au_milieu
>>> de_tram
array([3, 6, 9])

Une autre dimension

Si on travaille sur une liste plate, l’array est pratique, mais on en reste là. Néanmoins sa grande force est sa capacité à travailler sur plusieurs dimensions, et donc modifier tout aussi facilement des arrays d’arrays d’arrays d’arrays (arrête !) :

>>> thorique = array([ [1, 2, 3], [4, 5, 6], [7, 8, 9]])
>>> thorique
array([[1, 2, 3],
       [4, 5, 6],
       [7, 8, 9]])
>>> thorique ** 3
array([[  1,   8,  27],
       [ 64, 125, 216],
       [343, 512, 729]])

L’opération a été appliquée à tous les éléments sans faire de boucle, et en suivant l’imbrication de la structure de données récursivement.

Mais la partie la plus funky, c’est que le slicing AUSSI, peut se faire sur plusieurs dimensions.

Vous connaissez le slicing à une dimension :

>>> import random
>>> ticence = array([[random.randint(0, 100) for x in range(5)] for x in range(5)])
>>> ticence
array([[77, 44, 93, 65,  3],
       [ 8, 64, 36, 80, 77],
       [69, 24, 57, 18, 99],
       [60, 33, 63, 71, 99],
       [33, 60, 98, 85, 70]])
>>> ticence[1:4] # récupération des élément de 1 (inclus) à 4 (exclus)
array([[ 8, 64, 36, 80, 77],
       [69, 24, 57, 18, 99],
       [60, 33, 63, 71, 99]])

Mais avec un array numpy, on peut utiliser une virgule après le premier slicing, et mettre un nouveau slicing qui va travailler sur la dimension suivante.

Par exemple, ici j’applique le slice 1:4 sur la première dimension (je diminue le nombre de lignes) et ensuite j’applique le slicing 0:3 sur la seconde dimension (je diminue le nombre d’éléments de chaque lignes restantes, donc des colonnes).

>>> ticence[1:4, 0:3]
array([[ 8, 64, 36],
       [69, 24, 57],
       [60, 33, 63]])

Ca marche comme ça :

array[slicing_sur_dimension1, slicing_sur_dimension2, slicing_sur_dimension3, etc]

Si on commence à avoir beaucoup de dimensions et qu’on ne veut toucher que la dernière dimension, on peut utiliser Ellipsis.

>>> d2 = array([[random.randint(0, 100) for x in range(5)] for x in range(5)])
>>> d3 = array([d2.copy() for x in range(5)])
>>> d4 = array([d3.copy()  for x in range(5)])
>>> d5 = array([d4.copy()  for x in range(5)])
>>> d6 = array([d5.copy()  for x in range(5)])
>>> d6[1:3,...,-3:-1]
          [... plein de trucs ...]
          [[ 9, 86],
           [16, 40],
           [63, 26],
           [51,  5],
           [ 3, 46]],
 
          [[ 9, 86],
           [16, 40],
           [63, 26],
           [51,  5],
           [ 3, 46]]]]]])

La dernière ligne, on prend un tableau à 6 dimensions, on applique un slicing 1:3 sur la première dimension, et un slicing -3:-1 sur la dernière dimension.

Il est vrai que je ne m’en sers pas souvent.

Ok, je ne m’en suis jamais servi de toute ma vie. A part pour ce tuto. Mais c’est super classe non ?

Attention, cela ne marche que si toutes les dimensions ont le même nombre d’éléments. Cela se voit facilement en cas d’erreur car si le nombre d’éléments n’est pas bon, numpy va afficher votre array en ligne et pas sous forme de tabulaire :

>>> array([[1, 2, 5, 7, 9, 7, 8],[1,9]])
array([[1, 2, 5, 7, 9, 7, 8], [1, 9]], dtype=object)

Vous voyez en plus qu’il précise ici dtype=object, alors qu’il ne l’a pas fait plus haut.

Matplotlib pour afficher tout ça

En théorie, la lib matplotlib n’a rien à voir avec numpy. En pratique les utilisateurs de numpy utilisent très souvent numpy + matplotlib + ipython pour avoir une équivalent de matlab en Python.

Matplotlib est une lib qui permet de dessiner des graphes, mais qui a la particularité d’être orienté interaction. C’est à dire qu’elle est plus destinée à fabriquer votre graphe à la main, en bidouillant vos données, et possède dont des facilités pour cela.

D’abord on pip install matplotlib, et on prie pour que ça marche car sur certains OS ça plante méchamment.

Et ensuite dans son shell, on peut créer un petit graphe facilement sans trop se soucier des réglages, ceux par défaut étant pas mal :

from pylab import plot, xlabel, ylabel, title, legend
from numpy import sin, pi, linspace
 
# On active le mode interactif.
# Cela permet de voir notre graph
# en popup et de le modifier
# en temps réel.
ion()
 
# Utilisation de mes vagues connaissances
# de trigo pour pondre une sinusite...
# Heu, une sinusoide.
 
# Un array de 50 points répartis uniformément
# entre 0 et 2pi. Ca va nous servir de
# première coordonnée pour nos points.
x = linspace(0, 2 * pi)
 
# La fonction sin() de numpy va
# faire un nouvel array avec le sinus
# des points de l'array précédent.
# Ca nous fait notre deuxième coordonnée.
y = sin(x)
 
# On dessine la courbe, et on lui donne un pti nom
plot(x, y, label=u"Moi")
# Si je me suis pas trop planté ça devrait osciller
# entre 1 et -1
 
# On labellise les abscisses et les ordonnées
# car des données sans une échelle claire ne
# servent à rien.
ylabel(u"Self esteem (sur l'echelle de Richter)")
xlabel(u'Temps passé sur Dota (en joule par km)')
 
# On titre notre œuvre
title("Brace yourself, the graph is comming")
# On active la légende car le sujet est légendaire
legend()

Ce qui nous affiche :

Graphe de courbe sinusoidale

J'imagine toujours un petit train sur ce genre de courbe

Vous ne vous transformerez pas tout de suite en chercheur du CNRS après avoir lu ce tuto, mais j’espère qu’il vous aura donné un peu envie de faire mumuse avec Python pour manipuler vos données scientifiques.

flattr this!

Attend, pourquoi il me colore vars lui ?

mercredi 19 février 2014 à 00:10

Je connais tout en Python. On peut rien m’apprendre. Je suis un Dieu.

Mais pourquoi mon éditeur de texte me colore le nom de variable vars comme si c’était une fonction ? Je n’ai pas défini de…

Attend…

Nan…

Putain c’est un built-in !

Il y a encore des fonctions built-in que je ne connais pas non de nom !

>>> class UneClasse(object):
...     def __init__(self):
...         self.attribut = 'valeur'
...     def methode(self):
...         pass
...     
>>> vars(UneClasse()) # wut ?
{'attribut': u'valeur'}

En fait c’est l’équivalent de faire obj.__dict__, mais en propre. Tout comme next(iterable) est la version propre de iterable.__next__().

D’ailleurs on travaille sur une copie :

>>> id(object.__dict__)
33943288
>>> id(vars(object))
33942840

A chaque fois que je commence à me sentir enfin un programmeur de la mort, y a un détail pour me rappeler qu’il me reste toujours des bases à apprendre.

flattr this!

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

lundi 17 février 2014 à 21:37

La partie précédente vous a donné une vague idée de ce qu’était les tests unittaires, à quoi ça servait, et quelle forme ça avait.

Dans cette partie, nous allons aborder comment on rédige des tests unitaires avec la lib de standard de Python.

En effet, bien qu’on puisse se contenter de faire des assert et attendre que ça plante, ce n’est pas un moyen très efficace de faire ses tests. Des bibliothèques existent donc pour rendre le test plus puissant. En Python, c’est le module unittest qui s’en charge.

Article long, musique, tout ça…

Le test le plus simple

Pour l’exercice nous allons utiliser une fonction à tester qui soit un peu plus réaliste. Par exemple, les dictionnaires ont une méthode get() qui permet de récupérer une élément du dictionnaire. Si celui-ci n’existe pas, une valeur par défaut est retournée :

>>> simple_comme_bonjour = {"pomme": "banane", "geographie": "litterature"}
>>> simple_comme_bonjour.get("pomme", "je laisse la main")
"banane"
>>> simple_comme_bonjour.get("kamoulox", "je laisse la main")
"je laisse la main"

Une telle fonction n’existe pas pour les itérables, nous allons donc en créer une :

def get(lst, index, default=None):
    """
        Retourne l'élément de `lst` situé à `index`.
 
        Si aucun n'élément ne se trouve à `index`,
        retourne la valeur par défaut.
    """
    try:
        return lst[index]
    except IndexError:
        return default

Ça s’utilise ainsi :

>>> simple_comme_bonjour = ('pomme', 'banane')
>>> get(simple_comme_bonjour, 0, "je laisse la main")
'pomme'
>>> get(simple_comme_bonjour, 1000, "je laisse la main")
'je laisse la main'

Afin de sécuriser les futures améliorations de cette fonction, nous allons lui adjoindre des tests unitaires. Utiliser le module unitest est beaucoup plus verbeux que faire des assert, il suppose de faire une classe qui va regrouper tous les tests qu’on veut faire :

import unittest
 
# Le code à tester doit être importable. On
# verra dans une autre partie comment organiser
# son projet pour cela.
from mon_module import get
 
# Cette classe est un groupe de tests. Son nom DOIT commencer
# par 'Test' et la classe DOIT hériter de unittest.TestCase.
class TestFonctionGet(unittest.TestCase):
 
    # Chaque méthode dont le nom commence par 'test_'
    # est un test.
    def test_get_element(self):
 
        simple_comme_bonjour = ('pomme', 'banane')
        element = get(simple_comme_bonjour, 0)
 
        # Le test le plus simple est un test d'égalité. On se
        # sert de la méthode assertEqual pour dire que l'on
        # s'attend à ce que les deux éléments soient égaux. Sinon
        # le test échoue.
        self.assertEqual(element, 'pomme')
 
# Ceci lance le test si on exécute le script
# directement.
if __name__ == '__main__':
    unittest.main()

On met tout ça dans un fichier nommé “test_quelquechose”, et on l’exécute :

$ python test_get.py
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
 
OK

Cela signifie qu’un test a été exécuté (il y a un point affiché par test). Il n’y a eu aucune erreur.

Ajoutons un test pour essayer le cas où l’élément n’existe pas :

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')

La sortie nous montre maintenant deux tests passés sans erreur :

$ python test_get.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.001s
 
OK

Erreur durant le test

Il y a deux types d’erreur : une erreur logique (le code plante) ou un test qui échoue.

Commençons par le premier cas. Je rajoute une erreur à la noix :

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')
 
    # Ce code ne peut pas marcher car il n'y a pas 1000
    # éléments dans mon tuple.
    def test_avec_error(self):
        simple_comme_bonjour = ('pomme', 'banane')
        simple_comme_bonjour[1000]

Et zou :

$ 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)

Cette fois Python lance bien 3 tests, mais un génère une erreur. Il me signale laquelle par un “E” et donne la stacktrace qui permet de débugger le problème.

Vous voyez ici le premier intérêt d’utiliser un outil fait pour les tests plutôt que des assert à la main : Python n’a pas arrêté au premier plantage. Tous les tests ont été exécutés. Cela permet de savoir précisément quels tests parmi tous ceux que vous avez, sont touchés, et lesquels passent.

Retirons l’erreur logique, et ajoutons un un test qui échoue. Un test qui échoue c’est quand une méthode assertQuelquechose s’aperçoit que les valeurs ne correspondent pas à ce que le test voudrait.

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')
        # Ici j'ajoute ARTIFICIELLEMENT une erreur, mais on est bien d'accord
        # que normalement, si ça échoue ici, c'est que votre code ne se comporte
        # pas comme prévu. Personne ne nique ses tests volontairement, sauf
        # les rédacteurs de tutos et les étudiants en histoire de l'art.
        self.assertEqual(element, 'Je tres clair, Luc')
 
        # element ne sera pas égal à "Je tres clair, Luc", il sera égal à
        # 'Je laisse la main'. assertEqual va s'en rendre compte et va
        # déclarer que le test a échoué, puisque qu'elle vérifie l'égalité.

Et voici là nouvelle sortie :

$ 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)

L’échec est noté avec un F (pour Fail), et on vous donne le nom du test qui a échoué (c’est pour ça que je vous recommande de choisir un bon nom pour chaque test).

Là ça devient beaucoup plus intéressant qu’avec un assert à la main car vous voyez que non seulement on vous dit où est l’erreur, non seulement Python ne plante pas à la première erreur, mais en plus vous avez des informations supplémentaires.

Ici, cette information est :

AssertionError: 'Je laisse la main' != 'Je tres clair, Luc'
- Je laisse la main
+ Je tres clair, Luc

On vous dit très explicitement que c’est un échec sur une égalité, et voici les deux valeurs testées. Cela vous permet de rapidement identifier ce qui a merdé.

Il existe de nombreuses méthodes assertTruc, et chacune d’elles produit des informations différentes en cas d’échec.

Ah, certes

Il y a pas mal de méthodes assertBidule. En rédigeant ce tuto, sous Python 3, j’ai fait ça :

>>> [print (x) for x in dir(self) if x.startswith('assert')]
assertAlmostEqual
assertAlmostEquals
assertCountEqual
assertDictContainsSubset
assertDictEqual
assertEqual
assertEquals
assertFalse
assertGreater
assertGreaterEqual
assertIn
assertIs
assertIsInstance
assertIsNone
assertIsNot
assertIsNotNone
assertLess
assertLessEqual
assertListEqual
assertMultiLineEqual
assertNotAlmostEqual
assertNotAlmostEquals
assertNotEqual
assertNotEquals
assertNotIn
assertNotIsInstance
assertNotRegex
assertRaises
assertRaisesRegex
assertRaisesRegexp
assertRegex
assertRegexpMatches
assertSequenceEqual
assertSetEqual
assertTrue
assertTupleEqual
assertWarns
assertWarnsRegex

Selon votre installation de Python, vous en aurez plus où moins de nombreuses, et je vous invite à vous taper la doc pour voir tout ce qui s’offre à vous.

Voici quelques exemples des possibilités qui s’offrent à vous :

assertAlmostEqual

Va vérifier qu’un nombre est presque égal à un autre, à un arrondi près.

assertDictContainsSubset

Va vérifier que toutes les paires clé/valeur d’un dico sont contenues dans un autre.

assertRaises

Va vérifier que la fonction va lever une exception.

assertRegex

Va vérifier que la chaîne est validée par la regex donnée.

Setup et TearDown

Comme vous avez pu le constater dans notre exemple, à chaque test on recrée le tuple simple_comme_bonjour = ('pomme', 'banane'). Ce n’est pas très pratique. D’autant plus qu’on pourrait avoir des choses bien plus compliquées comme la connexion à une base de données ou une génération de données aléatoires.

Les méthodes setUp et tearDown sont là pour y pallier. Elles permettent respectivement de lancer un code avant chaque test et après chaque test.

class TestFonctionGet(unittest.TestCase):
 
    # Cette méthode sera appelée avant chaque test.
    def setUp(self):
        self.simple_comme_bonjour = ('pomme', 'banane')
 
    # Cette méthode sera appelée après chaque test.
    def tearDown(self):
        print('Nettoyage !')
 
    def test_get_element(self):
        # plus besoin de créer le tuple ici
        element = get(self.simple_comme_bonjour, 0)
        self.assertEqual(element, 'pomme')
 
    def test_element_manquant(self):
        element = get(self.simple_comme_bonjour, 1000, 'Je laisse la main')
        self.assertEqual(element, 'Je laisse la main')
 
    def test_avec_echec(self):
        element = get(self.simple_comme_bonjour, 1000, 'Je laisse la main')
        self.assertEqual(element, 'Je tres clair, Luc')
$ python test_get.py
Nettoyage !
FNettoyage !
.Nettoyage !
.
======================================================================
FAIL: test_avec_echec (__main__.TestFonctionGet)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_get.py", line 37, 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.001s
 
FAILED (failures=1)

Vous voyez qu’il n’y a plus besoin de créer le tuple 3 fois manuellement, c’est fait pour vous. De plus, la chaîne “Nettoyage !” est bien affichée 3 fois, malgré l’échec d’un test. En effet, ces méthodes sont toujours appelées, même en cas d’erreur. Cela permet de créer une environnement propre pour les tests, ou de nettoyer derrière (fermer un fichier, une connexion, etc).

Lancer plusieurs modules de test

Quand vous allez avoir plein de tests, vous n’allez pas tout mettre dans une classe, mais faire plein de fichiers avec des tests par sujet. Parfois vous ne lancerez qu’un fichier de test. Parfois vous voudrez tout lancer d’un coup.

Pour ce faire, assurez vous que vous vos modules de tests sont importables depuis le dossier où vous êtes. Tout doit être dans le PYTHON_PATH et les dossiers doivent contenir des fichiers __init__.py.

Ensuit, il suffit de lancer la commande :

python -m unittest discover

Python trouvera tous les fichiers de tests pour vous automatiquement, pourvu qu’ils soient nommés test_quelquechose.py.

La ptit’ clusion

Vous voyez maintenant comment utiliser un outil pour rédiger des tests, mais probablement pas COMMENT rédiger un test, ni QUAND et POUR QUOI. C’est tout à fait normal.

Dans les premières parties, je vais faire un tour de tous les outils de tests à votre disposition. Puis, dans les parties suivantes, je ferai un topo sur les questions existentielles du genre “ok mais je teste quel code ?” ou “à quel moment je mets un test” ou “pourquoi c’est moi qui écrit les tests et pas bob cet enculé ?”.

Bref, pour le moment, vous nagez un peu dans le brouillard, et il ne faut pas s’inquiéter. Le dossier sur les tests sera long et comportera surement 8 parties ou plus. Donc on se détend, et on profite du voyage.


Télécharger le code de l’article

flattr this!

Sondage! Aimeriez-vous un espace petites-annonces chez S&M ?

samedi 15 février 2014 à 17:53

Pendant que Sam vous mijotte des bons petits tutos à se lécher les babines moi je vais vour divertir un peu l’oignon avec ce petit vote.

On a quelques e-mails par mois assez surprenants, certains nous demandent si on ne veut pas bosser pour eux, d’autres veulent faire la publicité de leur app/produit, d’autres encore aimeraient nous rencontrer, voire même certaines coucher avec moi (ah non… merde)

On ne peut pas répondre favorablement à ces requêtes et c’est peut-être l’occasion de permettre à certains d’entre-vous d’échanger, vendre, se rencontrer via une sorte de pages jaunes/craiglist/ads (appelez ça comme vous voulez).

En gros des petites-annonces dans lesquelles nous définirons des catégories précises (on ne veut pas d’annonce pour vendre la recette miracle pour la repousse de la moustache).

On préfère vous demander avant car on estime que c’est une bonne chose de proposer plutôt que d’imposer. Et puis pour tâter le potentiel.

Les annonces sont bien évidement gratoches, on mettra peut-être des quotas si il y a des abus.

Quelques catégories pour donner une idée:
- Lans (parties)
- hack parties
- ventes de matos info
- trucs geek (gadgets, etc)
- demande de cours d’info
- donne des cours d’info
- donne des cours de math
- sallons info
- propositions de collaboration
- projets de conquête du monde
- demande d’aide sur des sujets particuliers
etc.

On a pas encore de liste précise des catégories mais il y a de quoi faire dans notre domaine. Si vous avez des idées quand aux catégories n’hésitez pas, balancez !

Aller, détente….

Note: There is a poll embedded within this post, please visit the site to participate in this post's poll.

flattr this!