簡體   English   中英

針對不同接口實現的共享 XCTest 單元測試

[英]Shared XCTest unit tests for different implementations of interface

我有一些接口(協議)的兩個或多個實現:

protocol Interface {
    func methodOne()
    func methodTwo()
}

我想測試每個實現,我不想重復代碼。 我有幾個選擇,但沒有一個能讓我滿意。

第一個是為ImplementationA創建測試用例並將其子類化以獲得ImplementationB的測試用例:

class ImplementationATests: XCTestCase {

    var implToTest: Interface!

    override func setUp() {
        super.setUp()
        implToTest = ImplementationA()
    }

    func testMethodOne() {
        ...
    }

    func testMethodTwo() {
        ...
    }
}


class ImplementationBTests: ImplementationATests {

    override func setUp() {
        super.setUp()
        implToTest = ImplementationB()
    }
}

這種方法的缺點之一是我無法進行僅適用於ImplementationA的測試。 (例如,測試特定於該實現的一些輔助方法)

我想到的第二個選項是為測試用例創建共享子類:

class InterfaceTests: XCTestCase {

    var implToTest: Interface!

    func testMethodOne() {
        ...
    }

    func testMethodTwo() {
        ...
    }
}

但在這里這些測試也將被執行,但它們將失敗,因為沒有為implToTest分配實現。 當然我可以為它分配一些實現,但是我將以相同實現的兩個測試用例結束。 最好的選擇是以某種方式禁用InterfaceTests測試用例並僅運行其子類。 是否可以?

我得到的第三個想法可能看起來很棘手,但它會滿足我的所有需求。 不幸的是它不起作用。 我決定創建InterfaceTestable協議:

protocol InterfaceTestable {
    var implToTest: Interface! { get set }
}

並使用所有共享測試對其進行擴展:

extension InterfaceTestable {

    func testMethodOne() {
        ...
    }

    func testMethodTwo() {
        ...
    }
}

然后為每個實現創建測試用例:

class ImplementationATests: XCTestCase, InterfaceTestable {

    var implToTest: Interface!

    override func setUp() {
        super.setUp()
        implToTest = ImplementationA()
    }

    // some tests which only apply to ImplementationA
}


class ImplementationBTests: XCTestCase, InterfaceTestable {

    var implToTest: Interface!       

    override func setUp() {
        super.setUp()
        implToTest = ImplementationB()
    }

    // some tests which only apply to ImplementationB
}

這些測試用例編譯但 Xcode 沒有看到在InterfaceTestable擴展中聲明的測試。

有沒有其他方法可以為不同的實現共享測試?

我遇到了同樣的問題,並使用第二個選項解決了該問題。 但是,我找到了一種方法來阻止基類中的測試用例運行:

覆蓋基類中的defaultTestSuite()類方法,以返回空的XCTestSuite

class InterfaceTests: XCTestCase {

  var implToTest: Interface!

  override class func defaultTestSuite() -> XCTestSuite {
    return XCTestSuite(name: "InterfaceTests Excluded")
  }
}

這樣就不會運行InterfaceTests測試。 不幸的是,也沒有對ImplementationATests測試。 通過覆蓋ImplementationATests defaultTestSuite() ,可以解決此問題:

class ImplementationATests : XCTestCase {

  override func setUp() {
    super.setUp()
    implToTest = ImplementationA()
  }

  override class func defaultTestSuite() -> XCTestSuite {
    return XCTestSuite(forTestCaseClass: ImplementationATests.self)
  } 
}

現在, ImplementationATests的測試套件將運行InterfaceTests所有測試,但是如果不設置implToTest ,則不會直接運行InterfaceTests測試。

我之前完成此操作的方式是使用共享基類。 使implToTest nillable。 在基類,如果不提供一個實現,只是return以保護條款進行的檢測。

有點煩人的是,該測試運行在不執行任何操作時包含了基類測試的報告。 但這是一個小麻煩。 測試子類將提供有用的反饋。

在 ithron 的解決方案之上構建,如果您仔細設計defaultTestSuite ,則可以消除每個子類重新覆蓋它的需要。

class InterfaceTests: XCTestCase {
    override class var defaultTestSuite: XCTestSuite {
        // When subclasses inherit this property, they'll fail this check, and hit the `else`.
        // At which point, they'll inherit the full test suite that generated for them.
        if self == AbstractTest.self {
            return XCTestSuite(name: "Empty suite for AbstractSpec")
        } else {
            return super.defaultTestSuite
        }
    }
}

當然,同樣的限制適用:這不會從 Xcode 測試導航器中隱藏空測試套件。

將其概括為AbstractTestCase class

我會 go 更進一步,制作一個基礎class AbstractTestCase: XCTestCase來存儲這個defaultTestSuite技巧,您的所有其他抽象類都可以從中繼承。

為了完整起見,要使其真正抽象,您還需要覆蓋所有XCTestCase初始值設定項,以便在嘗試實例化您的抽象類時使它們出錯。 在 Apple 的平台上,有 3 個初始化器需要覆蓋:

  1. -[XCTestCase init]
  2. -[XCTestCase initWithSelector:]
  3. -[XCTestCase initWithInvocation:]

不幸的是,這不能從 Swift 完成,因為最后一個初始化器使用NSInvocation NSInvocation不適用於 Swift(它與 Swift 的 ARC 不兼容)。 所以你需要在目標 C 中實現它。這是我的嘗試:

AbstractTestCase.h

#import <XCTest/XCTest.h>

@interface AbstractTestCase : XCTestCase

@end

AbstractTestCase.m

#import "AbstractTestCase.h"

@implementation AbstractTestCase

+ (XCTestSuite *)defaultTestSuite {
    if (self == [AbstractTestCase class]) {
        return [[XCTestSuite alloc] initWithName: @"Empty suite for AbstractTestCase"];
    } else {
        return [super defaultTestSuite];
    }
}

- (instancetype)init {
    self = [super init];
    NSAssert(![self isMemberOfClass:[AbstractTestCase class]], @"Do not instantiate this abstract class!");
    return self;
}

- (instancetype)initWithSelector:(SEL)selector {
    self = [super initWithSelector:selector];
    NSAssert(![self isMemberOfClass:[AbstractTestCase class]], @"Do not instantiate this abstract class!");
    return self;
}

- (instancetype)initWithInvocation:(NSInvocation *)invocation {
    self = [super initWithInvocation:invocation];
    NSAssert(![self isMemberOfClass:[AbstractTestCase class]], @"Do not instantiate this abstract class!");
    return self;
}

@end

用法

然后您可以將其用作抽象測試的超類,例如

class InterfaceTests: AbstractTestCase {
  var implToTest: Interface!

  func testSharedTest() {

  }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM