如何实现或模拟“抽象”OCUnit测试类?

时间:2010-03-05 09:30:14

标签: objective-c unit-testing dry ocunit class-hierarchy

我在继承层次结构中组织了许多Objective-C类。他们都共享一个共同的父母,实现孩子们共享的所有行为。每个子类定义了一些使其工作的方法,并且父类为设计为由其子项实现/覆盖的方法引发了异常。这有效地使父类成为一个伪抽象类(因为它本身没用),即使Objective-C没有明确支持抽象类。

这个问题的关键在于我使用OCUnit对这个类层次结构进行单元测试,并且测试的结构类似:一个测试类,它运行公共行为,子类对应于每个被测试的子类。但是,在(有效抽象)父类上运行测试用例是有问题的,因为如果没有关键方法,单元测试将以惊人的方式失败。 (在5个测试类中重复常见测试的替代方案实际上不是一个可接受的选项。)

我一直在使用的非理想解决方案是检查(在每个测试方法中)实例是否是父测试类,如果是,则进行挽救。这导致每个测试方法中的重复代码,如果一个单元测试是高度精细的,这个问题变得越来越烦人。此外,所有这些测试仍然执行并报告为成功,扭曲了实际运行的有意义测试的数量。

我更喜欢的是向OCUnit发出信号的方法“不要在这个类中运行任何测试,只在子类中运行它们。”据我所知,还没有一种方法可以做到这一点,类似于我可以实现/覆盖的+(BOOL)isAbstractTest方法。有关更好地解决这个问题的最小重复的任何想法? OCUnit是否有能力以这种方式标记测试类,或者是否需要提交Radar?


修改:这是link to the test code in question。请注意频繁重复if (...) return;以启动方法,包括使用NonConcreteClass()宏来简化。

5 个答案:

答案 0 :(得分:4)

这是一个对我有用的简单策略。只需覆盖AbstractTestCase中的invokeTest,如下所示:

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

答案 1 :(得分:1)

您还可以在抽象的TestCase类中覆盖+ (id)defaultTestSuite方法。

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

答案 2 :(得分:1)

听起来你想要parameterized test

只要您想要使用相同逻辑但变量不同的大量测试,参数化测试就很棒。在这种情况下,测试的参数将是具体测试的类,或者可能是一个将创建它的新实例的块。

有一篇关于在OCUnit here中实现参数化测试的文章。以下是将其应用于测试类层次结构的示例:

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

答案 3 :(得分:1)

最简单的方法:

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

复制,粘贴和替换AbstractClass

答案 4 :(得分:0)

如果不深入研究OCUnit本身,特别是-performTest的SenTestCase实现,我认为没有办法改进你当前的工作方式。如果它调用了一个方法来确定“我应该运行这个测试吗?”,你将被设置。默认实现将返回YES,而您的版本将类似于if语句。

我要提交雷达。最糟糕的情况是你的代码保持现在的状态。