我正在为一个白标签项目编写一个UI测试,其中每个应用程序都有一个不同的菜单项。测试在每个菜单项上点击并截图(使用快车道快照)。
目前,所有这些都发生在一个名为XCTestCase的testScreenshotAllMenuItems()中,它如下所示:
func testScreenshotAllMenuItems() {
// Take a screenshot of the menu
openTheMenu()
snapshot("Menu")
var cells:[XCUIElement] = []
// Store each menu item for use later
for i in 0..<app.tables.cells.count {
cells.append(app.tables.cells.element(boundBy: i))
}
// Loop through each menu item
for menuItem in cells.enumerated() {
let exists = menuItem.element.waitForExistence(timeout: 5)
if exists && menuItem.element.isHittable {
// Only tap on the menu item if it isn't an external link
let externalLink = menuItem.element.children(matching: .image)["external link"]
if !externalLink.exists {
var name = "\(menuItem.offset)"
let cellText = menuItem.element.children(matching: .staticText).firstMatch
if cellText.label != "" {
name += "-\(cellText.label.replacingOccurrences(of: " ", with: "-"))"
}
print("opening \(name)")
menuItem.element.tap()
// Screenshot this view and then re-open the menu
snapshot(name)
openTheMenu()
}
}
}
}我希望能够动态生成每个屏幕截图,因为它是自己的测试用例,这样就可以将它们正确地报告为单独的测试,比如:
[T] Screenshots
[t] testFavouritesViewScreenShot() ✓
[t] testGiveFeedbackViewScreenShot() ✓
[t] testSettingsViewScreenShot() ✓我看过以编程方式创建测试上的文档,但我不知道如何以一种快速的方式设置这些文档。-理想情况下,我会使用闭包将现有的屏幕截图测试封装到他们自己的XCTestCase中--我这样想,但似乎没有任何有用的init方法可以实现这一点:
for menuItem in cells {
let test = XCTestCase(closure: {
menuItem.tap()
snapshot("menuItemName")
})
test.run()
}我不明白文档建议使用的调用和选择器的组合,也找不到任何好的示例,请指出正确的方向,或者分享您对此工作的任何示例。
发布于 2019-03-17 05:18:23
您可能无法在纯NSInvocation中做到这一点,因为不再是swift的一部分了。
XCTest依赖于+ (NSArray<NSInvocation *> *)testInvocations函数来获取一个XCTestCase类中的测试方法列表。默认实现,您可以假设,只需查找以test前缀开头的所有方法,并返回包装在NSInvocation中的方法。(您可以阅读更多关于NSInvocation 这里的文章)
因此,如果我们希望在运行时声明测试,这是我们感兴趣的地方。
不幸的是,NSInvocation不再是快速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因此,现在我们的快速测试类只需要继承这个类并覆盖_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.称赞快速团队的超类实现。
编辑:我用示例github创建了回购
https://stackoverflow.com/questions/55142213
复制相似问题