Вы, вероятно, не можете сделать это чисто быстро, так как NSInvocation
больше не является частью быстрого API.
XCTest полагается на функцию + (NSArray<NSInvocation *> *)testInvocations
для получения списка методов тестирования внутри одного XCTestCase
класса.Реализация по умолчанию, как вы можете предположить, просто найти все методы, которые начинаются с префикса test
, и вернуть их, завернутые в NSInvocation
.(Вы можете прочитать больше о NSInvocation
здесь )
Поэтому, если мы хотим, чтобы тесты объявлялись во время выполнения, это нас интересует.
К сожалению NSInvocation
не является частьюswift api больше, и мы не можем переопределить этот метод.
Если вы можете использовать немного ObjC, тогда мы можем создать суперкласс, который скрывает детали NSInvocation внутри и предоставляет дружественный для swift 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
Так что теперь наши классы быстрых тестов должны просто наследовать от этого класса и переопределять _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.
Слава команде Quick для реализации суперкласса .
Редактировать : Я создал репо с примером github