Как реализовать или эмулировать «абстрактный» тестовый класс OCUnit? - PullRequest
3 голосов
/ 05 марта 2010

У меня есть ряд классов Objective C, организованных в иерархии наследования.Все они имеют общего родителя, который реализует все поведение, разделяемое между детьми.Каждый дочерний класс определяет несколько методов, которые заставляют его работать, и родительский класс вызывает исключение для методов, предназначенных для реализации / переопределения его дочерними классами.Это фактически делает родительский класс псевдо-абстрактным классом (поскольку он сам по себе бесполезен), хотя Objective-C не поддерживает явно абстрактные классы.

Суть этой проблемы заключается в том, что я тестирую этот модульиерархия классов с использованием OCUnit, и тесты структурированы аналогично: один тестовый класс, который выполняет общее поведение, с подклассом, соответствующим каждому из тестируемых дочерних классов.Тем не менее, запуск тестовых случаев в (эффективно абстрактном) родительском классе проблематичен, поскольку модульные тесты не пройдут эффектным образом без ключевых методов.(Альтернатива повторения общих тестов для 5 классов тестов на самом деле не является приемлемой.)

Неидеальное решение, которое я использовал, заключается в проверке (в каждом методе тестирования), является ли экземплярродительский тестовый класс, и выручить, если это так.Это приводит к повторному коду в каждом методе тестирования, и эта проблема становится все более раздражающей, если у вас очень детальные юнит-тесты.Кроме того, все такие тесты по-прежнему выполняются и сообщаются как об успешных, искажая количество значимых тестов, которые фактически были запущены.

Я бы предпочел, чтобы это был сигнал OCUnit "Не запускайте никаких тестовв этом классе запускайте их только в дочерних классах. "Насколько мне известно, пока нет способа сделать это, что-то похожее на +(BOOL)isAbstractTest метод, который я могу реализовать / переопределить.Есть идеи, как лучше решить эту проблему с минимальным повторением?Имеет ли OCUnit какую-либо возможность пометить тестовый класс таким образом, или пора подать радар?


Редактировать: Вот ссылка на тестовый кодпод вопросом .Обратите внимание на частое повторение if (...) return; для запуска метода, включая использование макроса NonConcreteClass() для краткости.

Ответы [ 5 ]

4 голосов
/ 27 мая 2011

Вот простая стратегия, которая сработала для меня.Просто переопределите invokeTest в вашем AbstractTestCase следующим образом:

- (void) invokeTest {
    BOOL abstractTest = [self isMemberOfClass:[AbstractTestCase class]];
    if(!abstractTest) [super invokeTest];
}
1 голос
/ 29 августа 2013

Самый простой способ:

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

Копирование, вставка и замена AbstractClass.

1 голос
/ 06 декабря 2012

Звучит так, как будто вы хотите параметризованный тест .

Параметризованные тесты хороши, когда вы хотите иметь большое количество тестов с одинаковой логикой, но разными переменными.В этом случае параметром для вашего теста будет конкретный протестированный класс или, возможно, блок, который создаст его новый экземпляр.

Здесь есть статья о реализации параметризованного тестирования в OCUnit здесь .Вот пример применения его для тестирования иерархии классов:

@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 голос
/ 10 апреля 2011

Вы также можете переопределить метод + (id)defaultTestSuite в своем абстрактном классе TestCase.

+ (id)defaultTestSuite {
    if ([self isEqual:[AbstractTestCase class]]) {
        return nil;
    }
    return [super defaultTestSuite];
}
0 голосов
/ 08 марта 2010

Я не вижу способа улучшить то, как вы в настоящее время делаете вещи, не углубляясь в сам OCUnit, в частности реализацию SenTestCase -performTest :. Вы были бы установлены, если бы он вызвал вызванный метод, чтобы определить, "Должен ли я запустить этот тест?" Реализация по умолчанию вернет YES, а ваша версия будет похожа на ваш оператор if.

Я бы подала Радар. Худшее, что может случиться, это то, что ваш код остается таким, какой он есть сейчас.

...