2008-09-18 18 views
19

Je suis à la recherche d'un outil qui peut prendre un test unitaire, commeGénérer des classes automatiquement à partir de tests unitaires?

IPerson p = new Person(); 
p.Name = "Sklivvz"; 
Assert.AreEqual("Sklivvz", p.Name); 

et générer, automatiquement, le talon classe et l'interface

interface IPerson   // inferred from IPerson p = new Person(); 
{ 
    string Name 
    { 
     get;    // inferred from Assert.AreEqual("Sklivvz", p.Name); 
     set;    // inferred from p.Name = "Sklivvz"; 
    } 
} 

class Person: IPerson  // inferred from IPerson p = new Person(); 
{ 
    private string name; // inferred from p.Name = "Sklivvz"; 

    public string Name // inferred from p.Name = "Sklivvz"; 
    { 
     get 
     { 
      return name; // inferred from Assert.AreEqual("Sklivvz", p.Name); 
     } 
     set 
     { 
      name = value; // inferred from p.Name = "Sklivvz"; 
     } 
    } 

    public Person()  // inferred from IPerson p = new Person(); 
    { 
    } 
} 

correspondant Je sais que ReSharper et Visual Studio font partie de ceux-ci, mais j'ai besoin d'un outil complet - ligne de commande ou autre - qui déduit automatiquement ce qui doit être fait. En l'absence d'un tel outil, comment l'écrivez-vous (par exemple, étendre ReSharper, à partir de rien, en utilisant quelles bibliothèques)?

+0

Vous pouvez le faire avec un [modèle T4.] (Http://msdn.microsoft.com/fr-fr/library/bb126445.aspx) Je n'ai pas d'exemple, mais je pourrais essayer d'écrire un, si vous êtes intéressé. Je pouvais voir comment cela pourrait être vraiment utile dans le développement de Test-First (TDD); l'astuce consiste à compiler votre code pendant que vous écrivez des tests pour des méthodes qui n'existent pas encore. L'autre problème, bien sûr, est que le modèle T4 doit probablement être plus intelligent que ce que vous avez décrit, et je doute que vous ayez fourni une spécification complète dans votre exemple. –

+0

@RobertHarvey, j'en discutais avec un collègue, et il a l'impression qu'il pourrait y avoir trop d'inférence à faire. Je serais juste heureux avec quelque chose qui fonctionne d'une manière beaucoup plus intelligente que la folie actuelle du «clic droit» :-) – Sklivvz

Répondre

4

Ce que vous semblez avoir besoin est un analyseur pour votre langage (Java), et un résolveur de noms et de types. ("Constructeur de table de symboles").Après l'analyse du texte source, un compilateur a généralement un résolveur de nom, qui essaye d'enregistrer la définition des noms et de leurs types correspondants, et un vérificateur de type, qui vérifie que chaque expression a un type valide.

Normalement, le résolveur de nom/type se plaint lorsqu'il ne trouve pas de définition. Ce que vous voulez qu'il fasse est de trouver la chose "indéfinie" qui cause le problème, et en déduire un type pour cela.

Pour

IPerson p = new Person(); 

le resolver sait que "personne" et "IPerson" ne sont pas définis. Si elle était

Foo p = new Bar(); 

il n'y aurait pas la moindre idée que vous vouliez une interface, juste que Foo est une sorte de parent abstrait de Bar (par exemple, une classe ou une interface). Ainsi, la décision telle qu'elle est doit être connue de l'outil ("chaque fois que vous trouvez une telle construction, supposons que Foo est une interface ..."). Vous pouvez utiliser une heuristique: IFoo et Foo signifient que IFoo devrait être une interface, et quelque part quelqu'un doit définir Foo comme une classe réalisant cette interface. Une fois l'outil a pris cette décision, il serait nécessaire de mettre à jour ses tables de symboles afin qu'il puisse passer à d'autres déclarations:

Pour

p.Name = "Sklivvz"; 

étant donné que p doit être une interface (par le inférence précédente), alors Name doit être un membre de champ, et il semble que son type soit String de l'affectation.

Avec cela, la déclaration:

Assert.AreEqual("Sklivvz", p.Name); 
noms et types

résoudre sans problème.

Le contenu des entités IFoo et Foo dépend de vous. vous n'avez pas à utiliser get and set mais c'est un goût personnel.

Cela ne fonctionnera pas si bien quand vous avez plusieurs entités dans la même déclaration:

x = p.a + p.b ; 

Nous savons a et b sont des champs probables, mais vous ne pouvez pas deviner quel type numérique si effectivement ils sont numeric, ou si ce sont des chaînes (ceci est légal pour les chaînes en Java, pas sur C#). Pour C++, vous ne savez même pas ce que «+» signifie; il pourrait s'agir d'un opérateur sur la classe Bar. Donc, ce que vous avez à faire est de collecter contraintes, par exemple, "est un nombre indéfini ou une chaîne", etc et que l'outil recueille des preuves, il réduit l'ensemble des contraintes possibles. (Cela fonctionne comme ces problèmes de mots: "Joe a sept fils Jeff est plus grand que Sam. Harry ne peut pas se cacher derrière Sam. Qui est le jumeau de Jeff?" Où vous devez collecter les preuves et supprimer les impossibilités. Vous devez également vous soucier du cas où vous vous retrouvez avec une contradiction.

Vous pouvez exclure le cas p.a + p.b, mais vous ne pouvez pas écrire vos tests unitaires en toute impunité. Il y a des solutions de résolution standards là-bas si vous voulez l'impunité. (Quel concept). OK, nous avons les idées, maintenant, cela peut-il être fait de façon pratique?

La première partie nécessite un analyseur et un résolveur de nom et de type pliable. Vous avez besoin d'un solveur de contraintes ou au moins d'une opération "valeur définie s'écoule vers une valeur indéfinie" (solveur de contraintes trivial).

Notre DMS Software Reengineering Toolkit avec ses Java Front End pourrait probablement le faire. DMS est un outil de création d'outils destiné aux personnes qui souhaitent créer des outils qui traitent les langages informatiques de manière arbitraire. (Pensez à "calculer avec des fragments de programme plutôt que des nombres"). Le DMS fournit une machine d'analyse d'usage général, et peut construire un arbre pour n'importe quelle extrémité avant (par exemple, Java, et il y a une extrémité avant C#). La raison pour laquelle j'ai choisi Java est que notre frontal Java a toutes ces machines de résolution de noms et de types, et il est fourni sous forme de source afin qu'il puisse être plié. Si vous vous êtes contenté du solveur de contraintes trivial, vous pourriez probablement plier le résolveur de nom Java pour comprendre les types. DMS vous permettra d'assembler des arbres qui correspondent à des fragments de code, et de les fusionner en plus gros; Lorsque votre outil a collecté des faits pour la table des symboles, il a pu construire les arbres primitifs.

Quelque part, vous devez décider que vous avez terminé. Combien de tests unitaires l'outil doit-il voir avant de connaître l'intégralité de l'interface? (Je suppose qu'il mange tous ceux que vous fournissez?). Une fois terminé, il assemble les fragments pour les différents membres et construit un AST pour une interface; DMS peut utiliser son prettyprinter pour convertir AST dans le code source comme vous l'avez montré.

Je suggère Java ici parce que notre frontal Java a une résolution de nom et de type. Notre extrémité avant C# ne fait pas. C'est une "simple" question d'ambition; quelqu'un doit en écrire un, mais c'est beaucoup de travail (du moins c'était pour Java et je ne peux pas imaginer que C# soit vraiment différent).

Mais l'idée fonctionne bien en principe en utilisant DMS.

Vous pouvez le faire avec une autre infrastructure qui vous donne accès à un analyseur et un résolveur de nom et de type pliable. Cela pourrait ne pas être si facile à obtenir pour C#; Je soupçonne que MS peut vous donner un analyseur, et l'accès à la résolution de nom et de type, mais aucun moyen de changer cela. Peut-être que Mono est la réponse?

Vous avez toujours besoin de générer des fragments de code et de les assembler. Vous pourriez essayer de le faire par piratage de chaîne; ma (longue) expérience avec le collage de morceaux de programme est que si vous le faites avec des cordes, vous finirez par en faire un bazar. Vous voulez vraiment des morceaux qui représentent des fragments de code de type connu, qui ne peuvent être combinés que de la façon dont la grammaire le permet; DMS le fait donc pas de gâchis.

-2

Je trouve que chaque fois que j'ai besoin d'un outil de génération de code comme celui-ci, j'écris probablement du code qui pourrait être rendu un peu plus générique, donc j'ai juste besoin de l'écrire une fois. Dans votre exemple, ces getters et setters ne semblent pas ajouter de valeur au code - en fait, il s'agit simplement d'affirmer que le mécanisme getter/setter en C# fonctionne. Je m'abstiendrais d'écrire (ou même d'utiliser) un tel outil avant de comprendre quelles sont les motivations pour écrire ce genre de tests.

BTW, vous voudrez peut-être jeter un oeil à NBehave?

1

Si vous envisagez d'écrire votre propre implémentation, je vous suggèrerais de jeter un coup d'œil aux moteurs de template NVelocity (C#) ou Velocity (Java).

Je les ai déjà utilisés dans un générateur de code et j'ai trouvé qu'ils facilitaient beaucoup le travail.

-2

J'utilise Rhino Mocks pour cela, quand j'ai juste besoin d'un simple bout.

http://www.ayende.com/wiki/Rhino+Mocks+-+Stubs.ashx

+0

Voici comment je le fais, dit tellement de code et de temps! –

+0

Il ne génère pas de fichiers ... Je veux que les stubs soient créés afin que je puisse les modifier manuellement. – Sklivvz

-1

navires Visual Studio avec quelques fonctionnalités qui peuvent être utiles pour vous ici:

Méthode Stub générons. Lorsque vous écrivez un appel à une méthode qui n'existe pas, vous obtenez une petite étiquette intelligente sur le nom de la méthode, que vous pouvez utiliser pour générer un talon de méthode en fonction des paramètres que vous passez.

Si vous êtes une personne du clavier (je suis), puis à droite après avoir tapé la parenthèse fermante, vous pouvez faire:

  • Ctrl-. (pour ouvrir la balise active)
  • ENTER (pour générer le stub)
  • F12 (passez à la définition, pour vous emmener à la nouvelle méthode)

La balise active apparaît uniquement si l'EDI pense qu'il n'y a pas une méthode qui correspond. Si vous souhaitez générer lorsque la balise active n'est pas activée, vous pouvez accéder à Édition-> Intellisense-> Générer la méthode Stub.

Snippets. Petits modèles de code qui facilitent la génération de bits de code commun. Certains sont simples (essayez "if [TAB] [TAB]").Certains sont complexes ('switch' va générer des cas pour une énumération). Vous pouvez aussi écrire le vôtre. Pour votre cas, essayez "class" et "prop".

Voir aussi "How to change “Generate Method Stub” to throw NotImplementedException in VS?" pour des extraits d'informations dans le contexte de GMS.

autoprops. Rappelez-vous que les propriétés peuvent être beaucoup plus simple:

public string Name { get; set; } 

créer la classe. Dans l'Explorateur de solutions, RCliquez sur le nom du projet ou un sous-dossier, sélectionnez Add-> Class. Tapez le nom de votre nouvelle classe. Appuyez sur . ENTREZ. Vous obtiendrez une déclaration de classe dans le bon espace de noms, etc.

Interface d'outil. Lorsque vous souhaitez qu'une classe implémente une interface, écrivez la partie de nom d'interface, activez la balise active et sélectionnez l'une ou l'autre option pour générer des stubs pour les membres de l'interface.

Ce ne sont pas tout à fait la solution 100% automatisée que vous recherchez, mais je pense que c'est une bonne atténuation.

+0

Merci, j'utilise déjà ces outils, mais ce qui m'intéresse est beaucoup plus sophistiqué, par exemple il devrait déduire de la cession et l'affirmer que la propriété est lu écrire – Sklivvz

0

J'aime CodeRush de DevExpress. Ils ont un énorme moteur de template personnalisable. Et le meilleur pour moi leur est aucune boîte de dialogue. Ils ont également des fonctionnalités pour créer des méthodes et des interfaces et des classes à partir d'une interface qui n'existe pas.

3

C'est incroyable comment personne n'a vraiment donné quoi que ce soit à ce que vous demandiez. Je ne connais pas la réponse, mais je vais donner mon avis à ce sujet.

Si j'essayais d'écrire quelque chose comme ça, je verrais probablement un plugin de resharper. La raison pour laquelle je dis cela est parce que, comme vous l'avez dit, resharper peut le faire, mais dans des étapes individuelles. Donc, j'écrirais quelque chose qui irait ligne par ligne et appliquer les méthodes de création de resharper appropriées enchaînées.

Maintenant, je ne sais même pas comment faire cela, car je n'ai jamais rien construit pour resharper, mais c'est ce que j'essayerais de faire. Il est logique que cela puisse être fait.

Et si vous écrivez du code, VEUILLEZ le publier, car je pourrais trouver cela utile, étant capable de générer tout le squelette en une seule étape. Très utile.

1

C'est faisable - du moins en théorie. Ce que je ferais est d'utiliser quelque chose comme csparser pour analyser le test unitaire (vous ne pouvez pas le compiler, malheureusement) et ensuite le prendre à partir de là. Le seul problème que je peux voir est que ce que vous faites est mal en termes de méthodologie - il est plus logique de générer des tests unitaires à partir de classes d'entités (en fait, Visual Studio fait précisément cela) que de faire l'inverse.

+0

Ce n'est pas une mauvaise méthodologie, juste différente. Il faut TDD comme base de développement. – MrJames

+0

@DmitriNesteruk Je viens de lire quelques-uns de vos doc de développement de plugins resharper ... comment pensez-vous qu'il serait difficile de créer un plugin pour cela? Comme le dit le PO, il fait déjà la plupart de ce dont il a besoin, il a seulement besoin de certaines des fonctionnalités existantes en une seule action. – Robbie

+0

@Robbie ReSharper en fait déjà beaucoup via «créer à partir de l'utilisation». Si vous en avez besoin en gros, vous pouvez automatiser les actions R # existantes pour effectuer de nombreux changements à la fois, et * cette * partie peut être un peu difficile. –

0

Essayez de regarder le Pex, un projet Microsoft sur les tests unitaires, ce qui est encore en recherche

research.microsoft.com/en-us/projects/Pex/

0

Je pense que ce que vous cherchez est un kit d'outils de fuzzing (https://en.wikipedia.org/wiki/Fuzz_testing).

Al Je n'utilisé dur, vous pourriez donner Randoop.NET une chance de générer des « tests unitaires » http://randoop.codeplex.com/

1

je pense une vraie solution à ce problème serait un analyseur très spécialisé. Comme ce n'est pas si facile à faire, j'ai une idée moins chère. Malheureusement, il faudrait changer la façon dont vous écrivez vos tests (à savoir, juste la création de l'objet):

dynamic p = someFactory.Create("MyNamespace.Person"); 
p.Name = "Sklivvz"; 
Assert.AreEqual("Sklivvz", p.Name); 

Un objet usine serait utilisé. S'il peut trouver l'objet nommé, il le créera et le renverra (c'est l'exécution normale du test). S'il ne le trouve pas, il créera un proxy d'enregistrement (un DynamicObject) qui enregistrera tous les appels et à la fin (peut-être sur le démontage) pourrait émettre des fichiers de classe (peut-être basés sur certains modèles) qui reflètent ce qu'il a vu " être appelé.

Quelques inconvénients que je vois:

  • besoin d'exécuter le code en mode « deux », ce qui est gênant.
  • Pour que le proxy puisse "voir" et enregistrer des appels, ils doivent être exécutés; Par exemple, le code dans un bloc catch doit être exécuté.
  • Vous devez modifier la façon dont vous créez votre objet testé.
  • Vous devez utiliser dynamic; vous perdrez la sécurité au moment de la compilation au cours des prochaines séries et cela a un impact sur les performances.

Le seul avantage que je vois est que c'est beaucoup moins cher pour créer qu'un analyseur spécialisé.