[英]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 個初始化器需要覆蓋:
-[XCTestCase init]
-[XCTestCase initWithSelector:]
-[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.