2010-03-05 5 views
3

J'ai un certain nombre de classes Objective-C organisées dans une hiérarchie d'héritage. Ils partagent tous un parent commun qui implémente tous les comportements partagés entre les enfants. Chaque classe enfant définit quelques méthodes qui le font fonctionner, et la classe parent déclenche une exception pour les méthodes conçues pour être implémentées/remplacées par ses enfants. Cela fait effectivement du parent une classe pseudo-abstraite (car inutile en soi) même si Objective-C ne supporte pas explicitement les classes abstraites. Le point crucial de ce problème est que je teste unitaire cette hiérarchie de classe en utilisant OCUnit, et que les tests sont structurés de manière similaire: une classe de test qui exerce le comportement commun, avec une sous-classe correspondant à chacune des classes enfants testées . Cependant, l'exécution des cas de test sur la classe parent (effectivement abstraite) est problématique, car les tests unitaires échoueront de façon spectaculaire sans les méthodes clés. (L'alternative de répéter les tests communs à travers 5 classes de test n'est pas vraiment une option acceptable.)Comment implémenter ou émuler une classe de test OCUnit "abstraite"?

La solution non idéale que j'ai utilisée est de vérifier (dans chaque méthode de test) si l'instance est le test parent classe, et renflouer si c'est le cas. Cela conduit à un code répété dans chaque méthode de test, un problème qui devient de plus en plus gênant si les tests unitaires sont très granulaires. En outre, tous ces tests sont toujours exécutés et signalés comme des succès, faussant le nombre de tests significatifs qui ont réellement été exécutés.

Ce que je préfère est un moyen de signaler à OCUnit « Ne pas exécuter des tests dans cette classe, ne les exécuter dans ses classes d'enfants. » À ma connaissance, il n'y a pas (encore) un moyen de le faire, quelque chose de similaire à une méthode +(BOOL)isAbstractTest que je peux implémenter/remplacer. Des idées sur une meilleure façon de résoudre ce problème avec une répétition minimale? Est-ce que OCUnit a la possibilité de marquer une classe de test de cette manière, ou est-il temps de classer un radar?


Edit: Voici un link to the test code in question. Notez la répétition fréquente de if (...) return; pour démarrer une méthode, y compris l'utilisation de la macro NonConcreteClass() par souci de concision.

+1

Donc, votre solution non idéale implique de commencer les tests avec quelque chose comme 'if ([self isMemberOfClass: [ParentTest class]]) return;'? –

+0

En gros, oui. J'ai modifié ma question pour inclure un lien vers la classe de test en question. –

Répondre

0

Je ne vois pas un moyen d'améliorer la façon dont vous êtes en train de faire des choses sans creuser en lui-même OCUnit, en particulier la mise en œuvre de SenTestCase -performTest :. Vous seriez défini s'il appelait une méthode pour déterminer "Dois-je exécuter ce test?" L'implémentation par défaut retournera YES, alors que votre version ressemblerait à votre if-statement.

Je déposerais un radar. Le pire qui pourrait arriver est que votre code reste comme il est maintenant.

+0

Ouais, je me suis dit - déjà classé un sur le 6ème. http://openradar.appspot.com/7725764 –

1

Vous pouvez également remplacer la méthode + (id)defaultTestSuite dans votre classe abstraite TestCase.

+ (id)defaultTestSuite { 
    if ([self isEqual:[AbstractTestCase class]]) { 
     return nil; 
    } 
    return [super defaultTestSuite]; 
} 
4

Voici une stratégie simple qui a fonctionné pour moi. Il suffit de passer outre invokeTest dans votre AbstractTestCase comme suit:

- (void) invokeTest { 
    BOOL abstractTest = [self isMemberOfClass:[AbstractTestCase class]]; 
    if(!abstractTest) [super invokeTest]; 
} 
+0

Très excellent! Merci. . . . J'étais sur le point de me lancer dans un tourbillon compliqué pour arriver à la même chose. –

1

On dirait que vous voulez un parameterized test.

les tests paramétrées sont parfaits à chaque fois que vous voulez avoir un grand nombre de tests avec la même logique, mais différentes variables. Dans ce cas, le paramètre de votre test sera la classe testée concrète, ou éventuellement un bloc qui en créera une nouvelle.

Il y a un article sur la mise en œuvre des tests paramétrés dans OCUnit here.Voici un exemple d'application à tester une hiérarchie de classes:

@implementation MyTestCase { 
    RPValue*(^_createInstance)(void); 
    MyClass *_instance; 
} 

+ (id)defaultTestSuite 
{ 
    SenTestSuite *testSuite = [[SenTestSuite alloc] initWithName:NSStringFromClass(self)]; 

    [self suite:testSuite addTestWithBlock:^id{ 
     return [[MyClass1 alloc] initWithAnArgument:someArgument]; 
    }]; 

    [self suite:testSuite addTestWithBlock:^id{ 
     return [[MyClass2 alloc] initWithAnotherArgument:someOtherArgument]; 
    }]; 

    return testSuite; 
} 

+ (void)suite:(SenTestSuite *)testSuite addTestWithBlock:(id(^)(void))block 
{ 
    for (NSInvocation *testInvocation in [self testInvocations]) { 
     [testSuite addTest:[[self alloc] initWithInvocation:testInvocation block:block]]; 
    } 
} 

- (id)initWithInvocation:(NSInvocation *)anInvocation block:(id(^)(void))block 
{ 
    self = [super initWithInvocation:anInvocation]; 
    if (!self) 
     return nil; 

    _createInstance = block; 

    return self; 
} 

- (void)setUp 
{ 
    _value = _createInstance(); 
} 

- (void)tearDown 
{ 
    _value = nil; 
} 
1

La façon la plus simple:

- (void)invokeTest { 
    [self isMemberOfClass:[AbstractClass class]] ?: [super invokeTest]; 
} 

Copier, coller et remplacer AbstractClass.

Questions connexes