Domanda

Ho un numero di classi oggettive-C organizzate in una gerarchia ereditaria. Condividono tutti un genitore comune che implementa tutti i comportamenti condivisi tra i bambini. Ogni classe figlio definisce alcuni metodi che lo fanno funzionare e la classe genitore solleva un'eccezione per i metodi progettati per essere implementati/sovrascritti dai suoi figli. Ciò rende effettivamente il genitore una classe pseudo-abstract (poiché è inutile da solo) anche se Objective-C non supporta esplicitamente le classi astratte.

Il punto cruciale di questo problema è che sto testando unità questa gerarchia di classe usando OCUNIT e i test sono strutturati in modo simile: una classe di test che esercita il comportamento comune, con una sottoclasse corrispondente a ciascuna delle classi di figli in test. Tuttavia, l'esecuzione dei casi di test sulla classe genitore (efficacemente astratta) è problematico, poiché i test unitari falliranno in modo spettacolare senza i metodi chiave. (L'alternativa di ripetere i test comuni tra 5 classi di test non è in realtà un'opzione accettabile.)

La soluzione non ideale che ho usato è controllare (in ciascun metodo di test) se l'istanza è la classe di test dei genitori e salvarsi se lo è. Ciò porta a un codice ripetuto in ogni metodo di prova, un problema che diventa sempre più fastidioso se i test unitari sono altamente granulari. Inoltre, tutti questi test sono ancora eseguiti e segnalati come successi, inclinando il numero di test significativi che sono stati effettivamente eseguiti.

Quello che preferirei è un modo per segnalare a OcUnit "Non eseguire test in questa classe, eseguirli solo nelle classi di bambini". Per quanto ne sappia, non c'è ancora un modo per farlo, qualcosa di simile a a +(BOOL)isAbstractTest Metodo che posso implementare/sostituire. Qualche idea su un modo migliore per risolvere questo problema con una ripetizione minima? OcUnit ha qualche capacità di contrassegnare una classe di test in questo modo o è tempo di archiviare un radar?


Modificare: Ecco un collegamento al codice di prova in questione. Notare la frequente ripetizione di if (...) return; per iniziare un metodo, incluso l'uso del NonConcreteClass() Macro per brevità.

È stato utile?

Soluzione

Non vedo un modo per migliorare il modo in cui stai attualmente facendo le cose senza scavare in Ocunit stessa, in particolare l'implementazione Sentestcase di -PerformTest:. Saresti impostato se si chiamasse invocato un metodo per determinare "dovrei eseguire questo test?" L'implementazione predefinita restituirebbe sì, mentre la tua versione sarebbe come la tua sezione.

Direi un radar. Il peggio che potrebbe accadere è che il tuo codice rimane così.

Altri suggerimenti

Ecco una semplice strategia che ha funzionato per me. Sovvaluta invoketest nella tua astratto come segue:

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

Potresti anche sovrascrivere + (id)defaultTestSuite Metodo nella tua classe di prova astratta.

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

Sembra che tu voglia un test parametrizzato.

I test parametrizzati sono fantastici ogni volta che si desidera avere un gran numero di test con la stessa logica ma variabili diverse. In questo caso, il parametro del test sarebbe la classe testata in calcestruzzo, o forse un blocco che ne creerà una nuova istanza.

C'è un articolo sull'implementazione di test parametrizzati in ocunit qui. Ecco un esempio di applicazione per testare una gerarchia di classe:

@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;
}

Nel modo più semplice:

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

Copia, incolla e sostituisci AbstractClass.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top