【问题标题】:How to implement or emulate an "abstract" OCUnit test class?如何实现或模拟“抽象”OCUnit 测试类?
【发布时间】:2011-01-24 01:08:45
【问题描述】:

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

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

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

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


编辑:这是link to the test code in question。注意if (...) return; 的频繁重复来启动一个方法,包括使用NonConcreteClass() 宏来简洁。

【问题讨论】:

  • 所以您的非理想解决方案包括使用 if ([self isMemberOfClass:[ParentTest class]]) return;? 之类的东西开始测试?
  • 粗略地说,是的。我已经编辑了我的问题以包含指向相关测试课程的链接。

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


【解决方案1】:

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

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

【讨论】:

【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

    • 非常好!谢谢。 . . .我正要开始一些复杂的混合来实现同样的目标。
    【解决方案4】:

    听起来您想要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;
    }
    

    【讨论】:

      【解决方案5】:

      最简单的方法:

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

      复制、粘贴和替换AbstractClass

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-01-01
        • 2014-07-22
        • 2019-10-01
        • 1970-01-01
        • 2020-03-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多