2008-10-31 2 views
6

Nous utilisons un modèle objet simple pour notre code réseau de bas niveau au travail où les pointeurs de structure sont passés à des fonctions qui prétendent être des méthodes. J'ai hérité la plupart de ce code qui a été écrit par des consultants avec une expérience C/C++ passable au mieux et j'ai passé beaucoup de nuits en essayant de refactoriser le code en quelque chose qui ressemblerait à une structure raisonnable.Comment est-ce que je moque des objets sans héritage (en C)?

Maintenant, je voudrais apporter le code sous test unitaire mais vu le modèle d'objet que nous avons choisi je n'ai aucune idée de comment se moquer des objets. Voir l'exemple ci-dessous:

tête de l'échantillon (foo.h):

#ifndef FOO_H_ 
#define FOO_H_ 

typedef struct Foo_s* Foo; 
Foo foo_create(TcpSocket tcp_socket); 
void foo_destroy(Foo foo); 
int foo_transmit_command(Foo foo, enum Command command); 

#endif /* FOO_H_ */ 

source d'échantillon (foo.c):

struct Foo_s { 
    TcpSocket tcp_socket; 
}; 

Foo foo_create(TcpSocket tcp_socket) 
{ 
    Foo foo = NULL; 

    assert(tcp_socket != NULL); 

    foo = malloc(sizeof(struct Foo_s)); 
    if (foo == NULL) { 
     goto fail; 
    } 
    memset(foo, 0UL, sizeof(struct Foo_s)); 

    foo->tcp_socket = tcp_socket; 

    return foo; 

fail: 
    foo_destroy(foo); 
    return NULL; 
} 

void foo_destroy(Foo foo) 
{ 
    if (foo != NULL) { 
      tcp_socket_destroy(foo->tcp_socket); 
      memset(foo, 0UL, sizeof(struct Foo_s)); 
      free(foo); 
    } 
} 

int foo_transmit_command(Foo foo, enum Command command) 
{ 
    size_t len = 0; 
    struct FooCommandPacket foo_command_packet = {0}; 

    assert(foo != NULL); 
    assert((Command_MIN <= command) && (command <= Command_MAX)); 

    /* Serialize command into foo_command_packet struct */ 
    ... 

    len = tcp_socket_send(foo->tcp_socket, &foo_command_packet, sizeof(foo_command_packet)); 
    if (len < sizeof(foo_command_packet)) { 
      return -1; 
    } 
    return 0; 
} 

Dans l'exemple ci-dessus, je voudrais se moquer de la TcpSocket objet afin que je puisse apporter "foo_transmit_command" sous test unitaire mais je ne suis pas sûr de savoir comment procéder à ce sujet sans héritage. Je ne veux pas vraiment reconcevoir le code pour utiliser vtables sauf si je dois vraiment le faire. Peut-être qu'il y a une meilleure approche à cela que de se moquer? Mon expérience de test vient principalement de C++ et j'ai un peu peur que je me sois peinte dans un coin ici. J'apprécierais hautement les recommandations de testeurs plus expérimentés.

Edit:
Comme Richard Quirk a souligné qu'il est vraiment l'appel à « tcp_socket_send » que je veux passer outre et je préférerais le faire sans enlever le vrai symbole de tcp_socket_send de la bibliothèque lors de la liaison le test depuis il est appelé par d'autres tests dans le même binaire.

Je commence à penser qu'il n'y a pas de solution évidente à ce problème ..

Répondre

3

Vous pouvez utiliser macro pour redéfinir tcp_socket_send-tcp_socket_send_moc et le lien avec la mise en œuvre réelle tcp_socket_send et factice pour tcp_socket_send_moc.
vous devrez choisir soigneusement l'endroit approprié pour:

#define tcp_socket_send tcp_socket_send_moc 
+0

Je pourrais probablement le mettre dans l'entête TcpSocket et le placer dans #ifdef TESTING .. #endif. Bonne suggestion. –

+0

Vous connaissez mieux votre code je voulais juste dire que la macro est une chose totale et la macro placée au mauvais endroit peut conduire au problème qui est vraiment difficile à déboguer – Ilya

0

Je ne sais pas ce que vous voulez atteindre.

Vous pouvez ajouter tous foo_ * fonctions en tant que membres du pointeur de fonction struct Foo_s mais vous devez encore passer explicitement pointeur sur votre objet comme il n'y a pas this implicite en C. Mais il vous donnera l'encapsulation et le polymorphisme.

+0

Cela m'obligerait à réécrire une grande partie de la base de code "juste" pour obtenir le code en tests unitaires, que j'éviterais de préférence. –

0

Quel système d'exploitation utilisez-vous? Je crois que vous pourriez faire un remplacement avec LD_PRELOAD sur GNU/Linux: This slide looks useful.

+0

Linux mais dans certains cas, les dépendances résident toutes dans la même bibliothèque, donc si je précharge une autre bibliothèque, je remplacerais aussi l'objet que je suis en train de tester. –

+0

Non, l'éditeur de liens ne trouverait que le seul symbole - tcp_socket_send - dans LD_PRELOAD, les autres seraient tous liés comme d'habitude. –

2

Jetez un oeil à TestDept: http://code.google.com/p/test-dept/

Il est un projet open source qui vise à fournir possiblité d'avoir des implémentations alternatives, par exemple stubs, de fonctions et pouvant changer en exécution quelle implémentation de cette fonction utiliser.

Tout est accompli en manipulant les fichiers objets qui sont très bien décrits sur la page d'accueil du projet.

+0

+1 - Cela ressemble à une excellente option de test de code embarqué de haut niveau, en particulier ceux de l'espace d'application ou de l'espace d'infrastructure. Merci! – tehnyit

0

Utiliser la macro pour affiner tcp_socket_send est bon. Mais le simulacre ne renvoie qu'un comportement. Ou vous avez besoin d'implémenter une variable dans la fonction fictive et la configurer différemment avant chaque test. Une autre méthode consiste à remplacer tcp_socket_send par point de fonction. Et les points à différentes fonctions simulées pour différents cas de test.

1

Alternativement, vous pouvez utiliser TestApe TestApe Unit testing for embedded software - Il peut le faire, mais notez qu'il est seulement C. Il irait comme ça ->

int mock_foo_transmit_command(Foo foo, enum Command command) { 
    VALIDATE(foo, a); 
    VALIDATE(command, b); 
} 

void test(void) { 
    EXPECT_VALIDATE(foo_transmit_command, mock_foo_transmit_command); 
    foo_transmit_command(a, b); 
} 
0

Pour ajouter à la réponse de Ilya. Tu peux le faire.

#define tcp_socket_send tcp_socket_send_moc 
#include "your_source_code.c" 

int tcp_socket_send_moc(...) 
{ ... } 

J'utilise la technique d'inclure le fichier source dans le module de tests unitaires afin de minimiser les modifications dans le fichier source lors de la création de tests unitaires.

Questions connexes