2010-04-20 5 views
6

J'ai essayé de commencer les tests unitaires en travaillant sur un petit programme cli. Mon programme fondamentalement parse les arguments de la ligne de commande et les options, et décide de la fonction à appeler. Chacune des fonctions effectue une opération sur une base de données.Comment réécrire ma base de données execute/commit pour la rendre compatible avec les tests unitaires?

Ainsi, par exemple, je pourrais avoir une fonction de création:

def create(self, opts, args): 
    #I've left out the error handling. 
    strtime = datetime.datetime.now().strftime("%D %H:%M") 
    vals = (strtime, opts.message, opts.keywords, False) 
    self.execute("insert into mytable values (?, ?, ?, ?)", vals) 
    self.commit() 

mon cas appel test cette fonction doit, puis exécutez la sélection SQL pour vérifier que la ligne a été saisie? Cela semble raisonnable, mais rend également les tests plus difficiles à maintenir. Voulez-vous réécrire la fonction pour retourner quelque chose et vérifier la valeur de retour?

Merci

+0

Je pense que les tests unitaires sont deux mots. –

Répondre

8

La réponse d'Alex couvre l'approche de l'injection de dépendance. Une autre est de factoriser votre méthode. En l'état, il comporte deux phases: construire une instruction SQL et exécuter l'instruction SQL. Vous ne voulez pas tester la deuxième phase: vous n'avez pas écrit le moteur SQL ou la base de données, vous pouvez supposer qu'ils fonctionnent correctement. La phase 1 est votre travail: construire une instruction SQL. Ainsi, vous pouvez réorganiser le code afin que vous puissiez tester simplement la phase 1:

def create_sql(self, opts, args): 
    #I've left out the error handling. 
    strtime = datetime.datetime.now().strftime("%D %H:%M") 
    vals = (strtime, opts.message, opts.keywords, False) 
    return "insert into mytable values (?, ?, ?, ?)", vals 

def create(self, opts, args): 
    self.execute(*self.create_sql(opts, args)) 
    self.commit() 

La fonction create_sql est la phase 1, et maintenant il est exprimé d'une manière qui vous permet d'écrire des tests directement contre: il prend des valeurs et renvoie des valeurs, de sorte que vous pouvez écrire des tests unitaires qui couvrent ses fonctionnalités. La fonction create elle-même est maintenant plus simple, et n'a pas besoin d'être testée de façon exhaustive: vous pouvez avoir quelques tests qui montrent qu'elle exécute vraiment SQL correctement, mais vous n'avez pas besoin de couvrir tous les cas de bords.

BTW: This video from Pycon (Tests and Testability) pourrait être intéressant.

6

Je certainement factoriser cette méthode pour faciliter l'essai - par exemple, l'injection de dépendance peut aider:

def create(self, opts, args, strtime=None, exec_and_commit=None): 
    #I've left out the error handling. 
    if strtime is None: 
     strtime = datetime.datetime.now().strftime("%D %H:%M") 
    vals = (strtime, opts.message, opts.keywords, False) 
    if exec_and_commit is None: 
     exec_and_commit = self.execute_and_commit 
    exec_and_commit("insert into mytable values (?, ?, ?, ?)", vals) 

Bien sûr, cela suppose que vous avez une méthode execute_and_commit qui appelle execute et commit méthodes. De cette façon, le code de test peut injecter une valeur connue pour strtime, et injecter son propre appelable comme exec_and_commit pour vérifier qu'il est appelé avec les arguments attendus.

0

Pas familier avec la syntaxe python, mais si vous êtes novice en tests unitaires, le moyen le plus simple de démarrer pourrait être d'extraire une méthode que vous passez dans les arguments de la ligne de commande et de récupérer la commande sql. Avec cette méthode, vous pouvez tester la partie de votre code où se trouve la vraie logique. Transmettez différents types d'arguments et vérifiez les résultats par rapport à la commande sql. Une fois que vous commencez à avoir le goût du test unitaire, vous pouvez en apprendre un peu plus sur l'injection de moquerie et de dépendance pour tester si vous appelez correctement les fonctions qui mettent à jour le db.

Espérons que vous êtes plus familiers avec java c syntaxe # que je suis avec la syntaxe python :)

public string GetSqlCommand(string[] commandLineArgs) 
{ 
    //do your parsing here 
    return sqlCommand; 
} 
[Test] 
public void emptyArgs_returnsEmptySqlCommand() 
{ 
    string expectedSqlCommand=""; 
    assert.AreEqual(expectedSqlCommand, GetSqlCommand(new string[]) 
} 
3

En général, vous avoir des tests unitaires en place avant refactoring, afin d'assurer qu'aucun changement de rupture sont faits. Et pourtant, certains refactoring peuvent être nécessaires pour permettre la testabilité ...un paradoxe malheureux, qui m'a déjà mordu. Cela dit, il existe quelques petits refactorings qui peuvent être effectués en toute sécurité, sans changer de comportement. Renommer et extraire sont deux.

Je vous recommande de jeter un coup d'œil au livre de Michael Feathers, Working with Legacy Code. Il se concentre sur le refactoring du code pour la testabilité. Les exemples sont en Java, mais les concepts s'appliqueraient aussi bien à Python.

+0

et le code C++ est utilisé dans le livre – Gutzofter

Questions connexes