3

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

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

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

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


编辑:这是有问题的测试代码的链接。注意启动方法的频繁重复if (...) return;,包括NonConcreteClass()为简洁起见使用宏。

4

5 回答 5

4

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

- (void) invokeTest {
    BOOL abstractTest = [self isMemberOfClass:[AbstractTestCase class]];
    if(!abstractTest) [super invokeTest];
}
于 2011-05-27T19:21:58.987 回答
1

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

+ (id)defaultTestSuite {
    if ([self isEqual:[AbstractTestCase class]]) {
        return nil;
    }
    return [super defaultTestSuite];
}
于 2011-04-09T23:01:39.007 回答
1

听起来你想要一个参数化的测试

每当您想要进行大量具有相同逻辑但变量不同的测试时,参数化测试非常有用。在这种情况下,测试的参数将是具体的测试类,或者可能是一个将创建它的新实例的块。

这里有一篇关于在 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;
}
于 2012-12-06T01:11:57.950 回答
1

最简单的方法:

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

复制、粘贴和替换AbstractClass

于 2013-08-29T12:25:55.180 回答
0

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

我会提交雷达。可能发生的最坏情况是您的代码保持现在的状态。

于 2010-03-08T06:46:26.133 回答