我在继承层次结构中组织了许多Objective-C类。他们都共享一个共同的父母,实现孩子们共享的所有行为。每个子类定义了一些使其工作的方法,并且父类为设计为由其子项实现/覆盖的方法引发了异常。这有效地使父类成为一个伪抽象类(因为它本身没用),即使Objective-C没有明确支持抽象类。
这个问题的关键在于我使用OCUnit对这个类层次结构进行单元测试,并且测试的结构类似:一个测试类,它运行公共行为,子类对应于每个被测试的子类。但是,在(有效抽象)父类上运行测试用例是有问题的,因为如果没有关键方法,单元测试将以惊人的方式失败。 (在5个测试类中重复常见测试的替代方案实际上不是一个可接受的选项。)
我一直在使用的非理想解决方案是检查(在每个测试方法中)实例是否是父测试类,如果是,则进行挽救。这导致每个测试方法中的重复代码,如果一个单元测试是高度精细的,这个问题变得越来越烦人。此外,所有这些测试仍然执行并报告为成功,扭曲了实际运行的有意义测试的数量。
我更喜欢的是向OCUnit发出信号的方法“不要在这个类中运行任何测试,只在子类中运行它们。”据我所知,还没有一种方法可以做到这一点,类似于我可以实现/覆盖的+(BOOL)isAbstractTest
方法。有关更好地解决这个问题的最小重复的任何想法? OCUnit是否有能力以这种方式标记测试类,或者是否需要提交Radar?
修改:这是link to the test code in question。请注意频繁重复if (...) return;
以启动方法,包括使用NonConcreteClass()
宏来简化。
答案 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语句。
我要提交雷达。最糟糕的情况是你的代码保持现在的状态。