开发者

How to implement or emulate an "abstract" OCUnit test class?

开发者 https://www.devze.com 2022-12-21 20:39 出处:网络
I have a number of Objective-C classes organized in an inheritance hierarchy. They all share a common parent which implements all the behaviors shared among the children. Each child class defines a fe

I have a number of Objective-C classes organized in an inheritance hierarchy. They all share a common parent which implements all the behaviors shared among the children. Each child class defines a few methods that make it work, and the parent class raises an exception for the methods designed to be implemented/overridden by its children. This effectively makes the parent a pseudo-abstract class (since it's useless on its own) even though Objective-C doesn't explicitly support abstract classes.

The crux of this problem is that I'm unit testing this class hierarchy using OCUnit, and the tests are structured similarly: one test class that exercises the common behavior, with a subclass corresponding to each of the child classes under test. However, running the test cases on the (effectively abstract) parent class is problematic, since the unit tests will fail in spectacular fashion without the key methods. (The alternative of repeating the common tests across 5 test classes is not really an acceptable option.)

The non-ideal solution I've been using is to check (in each test method) whether the instance is the parent test class, and bail out if it is. This leads to开发者_C百科 repeated code in every test method, a problem that becomes increasingly annoying if one's unit tests are highly granular. In addition, all such tests are still executed and reported as successes, skewing the number of meaningful tests that were actually run.

What I'd prefer is a way to signal to OCUnit "Don't run any tests in this class, only run them in its child classes." To my knowledge, there isn't (yet) a way to do that, something similar to a +(BOOL)isAbstractTest method I can implement/override. Any ideas on a better way to solve this problem with minimal repetition? Does OCUnit have any ability to flag a test class in this way, or is it time to file a Radar?


Edit: Here's a link to the test code in question. Notice the frequent repetition of if (...) return; to start a method, including use of the NonConcreteClass() macro for brevity.


Here's a simple strategy that worked for me. Just override invokeTest in your AbstractTestCase as follows:

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


You could also override + (id)defaultTestSuite method in your abstract TestCase class.

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


It sounds like you want a parameterized test.

Parameterized tests are great whenever you want to have a large number of tests with the same logic but different variables. In this case, the parameter to your test would be the concrete tested class, or possibly a block that will create a new instance of it.

There's an article about implementing parameterized testing in OCUnit here. Here's an example of applying it to testing a class hierarchy:

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


The simplest way:

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

Copy, paste and replace AbstractClass.


I don't see a way to improve on the way you're currently doing things without digging into OCUnit itself, specifically the SenTestCase implementation of -performTest:. You'd be set if it called invoked a method to determine "Should I run this test?" The default implementation would return YES, while your version would be like your if-statement.

I'd file a Radar. The worst that could happen is your code stays the way it is now.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号