Objective c 如何实施或模拟“;“摘要”;OCUnit测试类?

Objective c 如何实施或模拟“;“摘要”;OCUnit测试类?,objective-c,unit-testing,dry,ocunit,class-hierarchy,Objective C,Unit Testing,Dry,Ocunit,Class Hierarchy,我在继承层次结构中组织了许多Objective-C类。它们都共享一个共同的父对象,该父对象实现了子对象之间共享的所有行为。每个子类定义了一些使其工作的方法,父类为设计由其子类实现/重写的方法引发异常。即使Objective-C不显式支持抽象类,这也会有效地使父类成为伪抽象类(因为它本身是无用的) 这个问题的关键是,我正在使用OCUnit对这个类层次结构进行单元测试,测试的结构类似:一个测试类执行公共行为,子类对应于测试中的每个子类。然而,在(实际上是抽象的)父类上运行测试用例是有问题的,因为如果

我在继承层次结构中组织了许多Objective-C类。它们都共享一个共同的父对象,该父对象实现了子对象之间共享的所有行为。每个子类定义了一些使其工作的方法,父类为设计由其子类实现/重写的方法引发异常。即使Objective-C不显式支持抽象类,这也会有效地使父类成为伪抽象类(因为它本身是无用的)

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

我一直使用的非理想解决方案是(在每个测试方法中)检查实例是否是父测试类,如果是,则退出。这会导致在每个测试方法中重复代码,如果一个人的单元测试是高度细粒度的,这个问题会变得越来越烦人。此外,所有这样的测试仍然被执行并报告为成功,从而使实际运行的有意义的测试的数量出现偏差

我更喜欢的是一种向OCUnit发出信号的方式,“不要在这个类中运行任何测试,只在它的子类中运行它们。”据我所知,目前还没有一种方法可以做到这一点,类似于我可以实现/覆盖的
+(BOOL)isAbstractTest
方法。有没有更好的方法来解决这个问题,并且尽量减少重复?OCUnit是否能够以这种方式标记测试类,或者是时候提交雷达了



编辑:这里是一个示例。注意频繁重复的
if(…)返回
启动一个方法,包括使用
noncreteClass()
宏以简化操作。

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


我会用雷达。最糟糕的情况可能是代码保持现在的状态。

您还可以在抽象测试用例类中重写
+(id)defaultTestSuite
方法

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

这里有一个对我有效的简单策略。只需在AbstractTestCase中重写invokeTest,如下所示:

- (void) invokeTest {
    BOOL abstractTest = [self isMemberOfClass:[AbstractTestCase class]];
    if(!abstractTest) [super invokeTest];
}
听起来你想要一份工作

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

有一篇文章是关于在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;
}
最简单的方法是:

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

复制、粘贴和替换
AbstractClass

,因此您的非理想解决方案包括使用类似
if([self-isMemberOfClass:[parenttestclass]])返回的内容开始测试?粗略地说,是的。我已经编辑了我的问题,加入了一个到相关测试类的链接。是的,我想是这样的-已经在6号提交了一个。非常好!谢谢我正准备进行一些复杂的旋转运动来达到同样的效果。