你可能不能用纯斯威夫特
NSInvocation
不再是swift api的一部分。
依赖
+ (NSArray<NSInvocation *> *)testInvocations
函数以获取其中的测试方法列表
XCTestCase
班默认实现,正如您可以假设的那样,只需找到以
test
添加前缀并将其包装返回
NSInvocation
(你可以读更多关于
NSInvocation
here
所以,如果我们想在运行时声明测试,这是我们感兴趣的一点。
不幸地
NSInvocation
不再是swift api的一部分,我们不能重写此方法。
如果您同意使用一点ObjC,那么我们可以创建一个超级类,将NSInvocation细节隐藏在里面,并为子类提供快速友好的api。
/// Parent.h
/// SEL is just pointer on C struct so we cannot put it inside of NSArray.
/// Instead we use this class as wrapper.
@interface _QuickSelectorWrapper : NSObject
- (instancetype)initWithSelector:(SEL)selector;
@end
@interface ParametrizedTestCase : XCTestCase
/// List of test methods to call. By default return nothing
+ (NSArray<_QuickSelectorWrapper *> *)_qck_testMethodSelectors;
@end
/// Parent.m
#include "Parent.h"
@interface _QuickSelectorWrapper ()
@property(nonatomic, assign) SEL selector;
@end
@implementation _QuickSelectorWrapper
- (instancetype)initWithSelector:(SEL)selector {
self = [super init];
_selector = selector;
return self;
}
@end
@implementation ParametrizedTestCase
+ (NSArray<NSInvocation *> *)testInvocations {
// here we take list of test selectors from subclass
NSArray<_QuickSelectorWrapper *> *wrappers = [self _qck_testMethodSelectors];
NSMutableArray<NSInvocation *> *invocations = [NSMutableArray arrayWithCapacity:wrappers.count];
// And wrap them in NSInvocation as XCTest api require
for (_QuickSelectorWrapper *wrapper in wrappers) {
SEL selector = wrapper.selector;
NSMethodSignature *signature = [self instanceMethodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
invocation.selector = selector;
[invocations addObject:invocation];
}
/// If you want to mix parametrized test with normal `test_something` then you need to call super and append his invocations as well.
/// Otherwise `test`-prefixed methods will be ignored
return invocations;
}
+ (NSArray<_QuickSelectorWrapper *> *)_qck_testMethodSelectors {
return @[];
}
@end
所以现在我们的swift测试类需要从这个类继承并重写
_qck_testMethodSelectors
:
/// RuntimeTests.swift
class RuntimeTests: ParametrizedTestCase {
/// This is our parametrized method. For this example it just print out parameter value
func p(_ s: String) {
print("Magic: \(s)")
}
override class func _qck_testMethodSelectors() -> [_QuickSelectorWrapper] {
/// For this example we create 3 runtime tests "test_a", "test_b" and "test_c" with corresponding parameter
return ["a", "b", "c"].map { parameter in
/// first we wrap our test method in block that takes TestCase instance
let block: @convention(block) (RuntimeTests) -> Void = { $0.p(parameter) }
/// with help of ObjC runtime we add new test method to class
let implementation = imp_implementationWithBlock(block)
let selectorName = "test_\(parameter)"
let selector = NSSelectorFromString(selectorName)
class_addMethod(self, selector, implementation, "v@:")
/// and return wrapped selector on new created method
return _QuickSelectorWrapper(selector: selector)
}
}
}
预期产出:
Test Suite 'RuntimeTests' started at 2019-03-17 06:09:24.150
Test Case '-[ProtocolUnitTests.RuntimeTests test_a]' started.
Magic: a
Test Case '-[ProtocolUnitTests.RuntimeTests test_a]' passed (0.006 seconds).
Test Case '-[ProtocolUnitTests.RuntimeTests test_b]' started.
Magic: b
Test Case '-[ProtocolUnitTests.RuntimeTests test_b]' passed (0.001 seconds).
Test Case '-[ProtocolUnitTests.RuntimeTests test_c]' started.
Magic: c
Test Case '-[ProtocolUnitTests.RuntimeTests test_c]' passed (0.001 seconds).
Test Suite 'RuntimeTests' passed at 2019-03-17 06:09:24.159.
快速团队的荣誉
super class implementation
.
编辑
:我用例子创建了回购协议
github