Question

Je sais que c'est une question difficile et ouverte, mais j'ai pensé la poser et voir si quelqu'un avait des suggestions intéressantes.

J'ai développé un générateur de code qui amène notre interface Python à notre code C++ (généré via SWIG) et génère le code nécessaire pour l'exposer en tant que WebServices.Lorsque j'ai développé ce code, je l'ai fait en utilisant TDD, mais j'ai trouvé mes tests extrêmement fragiles.Parce que chaque test voulait essentiellement vérifier que pour un bit donné de code d'entrée (qui se trouve être un en-tête C++), j'obtiendrais un bit donné de code en sortie. J'ai écrit un petit moteur qui lit les définitions de test à partir des fichiers d'entrée XML et génère des tests. cas à partir de ces attentes.

Le problème est que j'ai peur de modifier le code.Cela et le fait que les tests unitaires eux-mêmes sont :complexe, et b :fragile.

J'essaie donc de réfléchir à des approches alternatives à ce problème, et il me semble que je l'aborde peut-être de la mauvaise manière.Peut-être que je dois me concentrer davantage sur le résultat, IE :le code que je génère s'exécute-t-il réellement et fait-il ce que je veux, plutôt que le code ressemble-t-il à ce que je souhaite.

Quelqu'un a-t-il vécu une expérience similaire à celle-ci et souhaiterait-il la partager ?

Était-ce utile?

La solution

J'ai commencé à rédiger un résumé de mon expérience avec mon propre générateur de code, puis je suis revenu en arrière et j'ai relu votre question et j'ai découvert que vous aviez déjà abordé les mêmes problèmes vous-même, en vous concentrant sur les résultats d'exécution plutôt que sur la disposition/l'apparence du code.

Le problème est que c'est difficile à tester, le code généré peut ne pas être adapté pour être réellement exécuté dans l'environnement du système de tests unitaires, et comment encodez-vous les résultats attendus ?

J'ai découvert que vous devez diviser le générateur de code en morceaux plus petits et les tester unitairement.Les tests unitaires d'un générateur de code complet ressemblent plus à des tests d'intégration qu'à des tests unitaires si vous me le demandez.

Autres conseils

Rappelez-vous que les « tests unitaires » ne sont qu’un type de test.Vous devriez pouvoir tester unitairement le interne des morceaux de votre générateur de code.Ce que vous regardez vraiment ici, ce sont des tests au niveau du système (c'est-à-direles tests de régression).Ce n'est pas seulement une question de sémantique...il existe différents mentalités, approches, attentes, etc.C'est certainement plus de travail, mais vous devrez probablement serrer les dents et mettre en place une suite de tests de régression de bout en bout :fichiers C++ corrigés -> interfaces SWIG -> modules python -> sortie connue.Vous voulez vraiment vérifier l'entrée connue (code C++ corrigé) par rapport à la sortie attendue (ce qui sort du programme Python final).Vérifier directement les résultats du générateur de code reviendrait à comparer des fichiers objets...

Oui, les résultats sont la SEULE chose qui compte.La vraie corvée consiste à écrire un framework qui permet à votre code généré de s'exécuter indépendamment...passez votre temps là-bas.

Si vous utilisez *nux, vous pouvez envisager de supprimer le framework unittest au profit d'un script bash ou d'un makefile.sous Windows, vous pourriez envisager de créer une application/fonction shell qui exécute le générateur, puis utilise le code (comme un autre processus) et le teste unitairement.

Une troisième option consisterait à générer le code, puis à créer une application à partir de celui-ci qui ne comprend rien d'autre qu'un test unitaire.Encore une fois, vous auriez besoin d'un script shell ou autre pour l'exécuter pour chaque entrée.Quant à la façon de coder le comportement attendu, il me semble que cela pourrait être fait à peu près de la même manière que vous le feriez pour le code C++ en utilisant simplement l'interface générée plutôt que celle C++.

Je voulais juste souligner que vous pouvez toujours réaliser des tests plus fins tout en vérifiant les résultats :vous pouvez tester des morceaux de code individuels en les imbriquant dans du code de configuration et de vérification :

int x = 0;
GENERATED_CODE
assert(x == 100);

À condition que votre code généré soit assemblé à partir de morceaux plus petits et que les morceaux ne changent pas fréquemment, vous pouvez exercer plus de conditions et tester un peu mieux, et, espérons-le, éviter que tous vos tests ne s'interrompent lorsque vous modifiez les détails d'un morceau.

Les tests unitaires consistent simplement à tester une unité spécifique.Donc, si vous écrivez une spécification pour la classe A, l'idéal est que la classe A ne dispose pas des versions concrètes réelles des classes B et C.

Ok j'ai remarqué par la suite que la balise de cette question inclut C++/Python, mais les principes sont les mêmes :

    public class A : InterfaceA 
    {   
      InterfaceB b;

      InterfaceC c;

      public A(InterfaceB b, InterfaceC c)   {
          this._b = b;
          this._c = c;   }

      public string SomeOperation(string input)   
      {
          return this._b.SomeOtherOperation(input) 
               + this._c.EvenAnotherOperation(input); 
      } 
    }

Étant donné que le système A ci-dessus injecte des interfaces vers les systèmes B et C, vous pouvez tester unitairement uniquement le système A, sans que les fonctionnalités réelles ne soient exécutées par un autre système.Il s’agit de tests unitaires.

Voici une manière intelligente d'aborder un système de sa création à son achèvement, avec une spécification When différente pour chaque élément de comportement :

public class When_system_A_has_some_operation_called_with_valid_input : SystemASpecification
{
    private string _actualString;

    private string _expectedString;

    private string _input;

    private string _returnB;

    private string _returnC;

    [It]
    public void Should_return_the_expected_string()
    {
        _actualString.Should().Be.EqualTo(this._expectedString);
    }

    public override void GivenThat()
    {
        var randomGenerator = new RandomGenerator();
        this._input = randomGenerator.Generate<string>();
        this._returnB = randomGenerator.Generate<string>();
        this._returnC = randomGenerator.Generate<string>();

        Dep<InterfaceB>().Stub(b => b.SomeOtherOperation(_input))
                         .Return(this._returnB);
        Dep<InterfaceC>().Stub(c => c.EvenAnotherOperation(_input))
                         .Return(this._returnC);

        this._expectedString = this._returnB + this._returnC;
    }

    public override void WhenIRun()
    {
        this._actualString = Sut.SomeOperation(this._input);
    }
}

Donc, en conclusion, une seule unité/spécification peut avoir plusieurs comportements, et la spécification grandit à mesure que vous développez l'unité/le système ;et si votre système testé dépend d'autres systèmes concrets en son sein, faites attention.

Ma recommandation serait de déterminer un ensemble de résultats d'entrée-sortie connus, tels que des cas plus simples que vous avez déjà en place, et tester unitairement le code produit.Il est tout à fait possible que lorsque vous changez de générateur, la chaîne exacte produite soit légèrement différente...mais ce qui vous importe vraiment, c'est de savoir si cela est interprété de la même manière.Ainsi, si vous testez les résultats comme vous testeriez ce code s’il s’agissait de votre fonctionnalité, vous saurez s’il réussit comme vous le souhaitez.

Fondamentalement, ce que vous voulez vraiment savoir, c'est si votre générateur produira ce que vous attendez sans tester physiquement toutes les combinaisons possibles (également :impossible).En vous assurant que votre générateur est conforme à vos attentes, vous pouvez être sûr que le générateur réussira dans des situations de plus en plus complexes.

De cette manière, vous pouvez également créer une suite de tests de régression (tests unitaires qui doivent continuer à fonctionner correctement).Cela vous aidera à vous assurer que les modifications apportées à votre générateur ne brisent pas d'autres formes de code.Lorsque vous rencontrez un bug que vos tests unitaires n'ont pas détecté, vous souhaiterez peut-être l'inclure pour éviter une panne similaire.

Je trouve que vous devez tester ce que vous générez davantage que la façon dont vous le générez.

Dans mon cas, le programme génère de nombreux types de code (C#, HTML, SCSS, JS, etc.) qui se compilent dans une application Web.La meilleure façon que j'ai trouvée pour réduire globalement les bogues de régression est de tester l'application Web elle-même, et non de tester le générateur.

Ne vous méprenez pas, il existe encore des tests unitaires vérifiant une partie du code du générateur, mais notre plus gros rapport qualité-prix a été les tests d'interface utilisateur sur l'application générée elle-même.

Puisque nous le générons, nous générons également une belle abstraction en JS que nous pouvons utiliser pour tester l'application par programmation.Nous avons suivi quelques idées décrites ici : http://code.tutsplus.com/articles/maintainable-automated-ui-tests--net-35089

L'avantage est qu'il teste réellement votre système de bout en bout, depuis la génération du code jusqu'à ce que vous générez réellement.Une fois qu’un test échoue, il est facile de remonter jusqu’à l’endroit où le générateur est tombé en panne.

C'est plutôt doux.

Bonne chance!

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top