Frage

Ich habe eine Reihe von Objektivklassen, die in einer Vererbungshierarchie organisiert sind. Sie alle teilen einen gemeinsamen Elternteil, der alle Verhaltensweisen implementiert, die unter den Kindern geteilt werden. Jede untergeordnete Klasse definiert einige Methoden, die sie zum Laufen bringen, und die Elternklasse erhebt eine Ausnahme für die von ihren Kindern implementierten/überschriebenen Methoden. Dies macht den Elternteil effektiv zu einer Pseudo-Abstract-Klasse (da es für sich genommen nutzlos ist), obwohl objektiv-c abstrakte Klassen nicht explizit unterstützt.

Der Kern dieses Problems ist, dass ich Unit -Testen dieser Klassenhierarchie mit Ocunit teste und die Tests ähnlich strukturiert sind: eine Testklasse, die das gemeinsame Verhalten ausübt, wobei eine Unterklasse jeder der untersuchten untergeordneten untergeordneten Klassen entspricht. Das Ausführen der Testfälle auf der (effektiv abstrakten) Elternklasse ist jedoch problematisch, da die Unit -Tests ohne die Schlüsselmethoden spektakulär fehlschlagen. (Die Alternative zur Wiederholung der gemeinsamen Tests in 5 Testklassen ist nicht wirklich eine akzeptable Option.)

Die nicht ideale Lösung, die ich verwendete, besteht darin, (in jeder Testmethode) zu überprüfen, ob die Instanz die übergeordnete Testklasse ist, und retten, ob dies der Fall ist. Dies führt zu einem wiederholten Code in jeder Testmethode, ein Problem, das immer ärgerlicher wird, wenn die Einheiten -Tests hochkörnig sind. Darüber hinaus werden alle dieser Tests weiterhin ausgeführt und als Erfolge gemeldet, wodurch die Anzahl der aussagekräftigen Tests verzerrt wird, die tatsächlich durchgeführt wurden.

Was ich bevorzugen würde, ist eine Möglichkeit, Ocunit zu signalisieren: "Führen Sie in dieser Klasse keine Tests durch, sondern leiten Sie sie nur in seinen Kinderklassen aus." Meines Wissens gibt es (noch) eine Möglichkeit, das zu tun, etwas ähnlich wie a +(BOOL)isAbstractTest Methode, die ich implementieren/überschreiben kann. Irgendwelche Ideen, um dieses Problem mit der minimalen Wiederholung besser zu lösen? Hat Ocunit eine Testklasse auf diese Weise oder ist es Zeit, ein Radar einzureichen?


Bearbeiten: Hier ist ein Link zum fraglichen Testcode. Beachten Sie die häufige Wiederholung von if (...) return; um eine Methode zu starten, einschließlich der Verwendung des NonConcreteClass() Makro für Kürze.

War es hilfreich?

Lösung

Ich sehe keinen Weg, um die Art und Weise zu verbessern, wie Sie derzeit Dinge tun, ohne sich in Ocunit selbst zu graben, insbesondere die Sentestcase -Implementierung von -performTest:. Sie werden festgelegt, wenn sie eine Methode bezeichnet, um festzustellen: "Soll ich diesen Test ausführen?" Die Standardimplementierung würde Ja zurückgeben, während Ihre Version wie Ihre If-Statement ähnelt.

Ich würde ein Radar einreichen. Das Schlimmste, was passieren könnte, ist, dass Ihr Code so bleibt, wie er jetzt ist.

Andere Tipps

Hier ist eine einfache Strategie, die für mich funktioniert hat. Überschreiben Sie in Ihrem AbstractTest Case einfach wie folgt:

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

Sie können auch überschreiben + (id)defaultTestSuite Methode in Ihrer abstrakten Testpase -Klasse.

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

Es hört sich so an, als ob du eine willst Parametrisierter Test.

Parametrisierte Tests sind großartig, wenn Sie eine große Anzahl von Tests mit derselben Logik, aber unterschiedlichen Variablen haben möchten. In diesem Fall wäre der Parameter für Ihren Test die konkrete getestete Klasse oder möglicherweise ein Block, der eine neue Instanz davon erzeugt.

Es gibt einen Artikel über die Implementierung parametrisierter Tests in Ocunit hier. Hier ist ein Beispiel, um es auf das Testen einer Klassenhierarchie anzuwenden:

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

Der einfachste Weg:

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

Kopieren, einfügen und ersetzen AbstractClass.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top