3

継承階層で編成された多数の Objective-C クラスがあります。それらはすべて、子の間で共有されるすべての動作を実装する共通の親を共有します。各子クラスはそれを機能させるいくつかのメソッドを定義し、親クラスはその子によって実装/オーバーライドされるように設計されたメソッドに対して例外を発生させます。これにより、Objective-C が抽象クラスを明示的にサポートしていなくても、実質的に親が疑似抽象クラスになります (それ自体では役に立たないため)。

この問題の核心は、OCUnit を使用してこのクラス階層を単体テストしていることです。テストは同様に構造化されています。つまり、共通の動作を実行する 1 つのテスト クラスと、テスト対象の各子クラスに対応するサブクラスがあります。ただし、(実質的に抽象化された) 親クラスでテスト ケースを実行することには問題があります。これは、主要なメソッドがないと、ユニット テストが見事に失敗するためです。(5 つのテスト クラスで共通のテストを繰り返すという代替案は、実際には受け入れられる選択肢ではありません。)

私が使用してきた理想的ではない解決策は、(各テスト メソッドで) インスタンスが親テスト クラスであるかどうかを確認し、そうである場合は救済することです。これにより、すべてのテスト メソッドでコードが繰り返されることになり、単体テストが非常に粒度が高い場合、この問題はますます厄介になります。さらに、そのようなすべてのテストは引き続き実行され、成功として報告されるため、実際に実行された意味のあるテストの数が歪められます。

私が望むのは、OCUnit に「このクラスではテストを実行せず、その子クラスでのみ実行してください」というシグナルを送る方法です。私の知る限り、それを行う方法は(まだ)ありません+(BOOL)isAbstractTest。実装/オーバーライドできる方法に似ています。最小限の繰り返しでこの問題を解決するためのより良い方法についてのアイデアはありますか? OCUnit には、この方法でテスト クラスにフラグを立てる機能がありますか?それとも、レーダーを提出する時が来ましたか?


編集:問題のテスト コードへのリンクは次のとおりです。簡潔にするためのマクロのif (...) return;使用を含め、メソッドを開始するためにが頻繁に繰り返されていることに注意してください。NonConcreteClass()

4

5 に答える 5

1

パラメータ化された testが必要なようです。

パラメーター化されたテストは、ロジックが同じで変数が異なる多数のテストが必要な場合に最適です。この場合、テストのパラメーターは具体的なテスト済みクラス、またはその新しいインスタンスを作成するブロックになります。

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

+ (id)defaultTestSuite抽象 TestCase クラスでメソッドをオーバーライドすることもできます。

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

OCUnit 自体、特に -performTest: の SenTestCase 実装を掘り下げずに、現在のやり方を改善する方法はありません。「このテストを実行する必要がありますか?」デフォルトの実装は YES を返しますが、バージョンは if ステートメントのようになります。

レーダーを提出します。起こりうる最悪の事態は、コードが現在のままであるということです。

于 2010-03-08T06:46:26.133 に答える