ADLayoutTest
ADLayoutTest 是对 UI 布局基于属性的测试实现的示例。
主要思路是生成随机关联视图输入(使用 SwiftCheck),布局视图,然后断言一些布局属性是真(使用 ADAssertLayout)。
我们可以利用同样的思路制作快照测试。
特点
示例
您可以在 示例 目录中找到使用示例。
假设我们在应用程序中使用了以下视图,并希望对其进行测试
import Foundation
import UIKit
struct ExampleViewModel {
let image: UIImage?
let text: String
let subText: String
}
class ExampleView: UIView {
@IBOutlet var imageView: UIImageView!
@IBOutlet var label: UILabel!
@IBOutlet var subLabel: UILabel!
// MARK: - Public
func configure(with viewModel: ExampleViewModel) {
imageView.image = viewModel.image
label.text = viewModel.text
subLabel.text = viewModel.subText
}
}
我们希望生成随机视图模型以配置视图。为此,我们使用 SwiftCheck 使 ExampleViewModel
符合 Arbitrary
。这样我们就有了一个 ExampleViewModel
的随机生成器。
import SwiftCheck
extension ExampleViewModel: Arbitrary {
// This is a random generator of `ExampleViewModel`
public static var arbitrary: Gen<ExampleViewModel> {
return Gen<ExampleViewModel>.compose { c in
return ExampleViewModel(
image: c.generate(using: .image(min: 20, max: 100)),
text: c.generate(using: .words),
subText: c.generate(using: .words)
)
}
}
}
.image(min: 20, max: 100)
和 .words
是创建随机图片或字符串实例的 自定义 生成器。
一个基本的测试用例用于验证 ExampleView
布局是
import XCTest
import ADAssertLayout
import ADLayoutTest
class ADLayoutTest_ExampleTests: XCTestCase {
func testExampleView() {
runLayoutTests(named: "ExampleView") { (viewModel: ExampleViewModel) in
// create the view we want to test
let view: ExampleView = ExampleView.fromNib(named: "ExampleViewValid")
// setup the view and pass the random view model
view.backgroundColor = UIColor.white
view.frame = CGRect(x: 0, y: 0, width: 320.0, height: 150.0)
view.configure(with: viewModel)
// layout the view
view.setNeedsLayout()
view.layoutIfNeeded()
// run layout assertions (no view overlap, no ambiguous layout, etc)
do {
try view.ad_runBasicRecursiveTests()
} catch {
return .failure(view, error)
}
return .success(view)
}
}
}
如果有错误发生,您可以在日志中找到原因。
Test Case '-[ADLayoutTest_ExampleTests.ADLayoutTest_ExampleTests testExampleViewError]' started.
/Users/felginep/Sources/ADLayoutTest/Example/ADLayoutTest_ExampleTests/ADLayoutTest_ExampleTests.swift:83: error: -[ADLayoutTest_ExampleTests.ADLayoutTest_ExampleTests testExampleViewError] : failed - Bottom right corner of <UIImageView: 0x7f8af7c293e0; frame = (20 20; 70 70); clipsToBounds = YES; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <CALayer: 0x60000220cd20>> (superview: <ADLayoutTest_Example.ExampleView: 0x7f8af7c291e0; frame = (0 0; 320 150); autoresize = W+H; layer = <CALayer: 0x60000220ce00>>) overlaps upper left corner of <UILabel: 0x7f8af7c29610; frame = (62 20; 238 20.5); text = 'Erat porta eget venenatis...'; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600000159770>> (superview: <ADLayoutTest_Example.ExampleView: 0x7f8af7c291e0; frame = (0 0; 320 150); autoresize = W+H; layer = <CALayer: 0x60000220ce00>>).
*** Failed! Proposition: ExampleView Overlap
Falsifiable (after 9 tests):
ExampleViewModel(image: Optional(<UIImage: 0x6000009a4380>, {36, 79}), text: "Erat porta eget venenatis porta", subText: "Elit elit etiam dapibus at erat")
*** Passed 8 tests
.
error: -[ADLayoutTest_ExampleTests.ADLayoutTest_ExampleTests testExampleViewError] : failed - Falsifiable; Replay with 904979560 363827163 and size 8
请注意,您可以看到哪个输入导致了布局错误
ExampleViewModel(
image: Optional(<UIImage: 0x6000009a4380>, {36, 79}),
text: "Erat porta eget venenatis porta",
subText: "Elit elit etiam dapibus at erat"
)
如果您想为调试目的重现相同的错误,您可以使用日志消息来使用自定义随机策略 .replay
重放测试。
Falsifiable; Replay with 904979560 363827163 and size 8
然后参数是
.replay(
seed1: 904979560,
seed2: 363827163,
size: 8
)
如果您进入测试报告,您可以在其中有布局错误的失败视图的快照。
参数
可以向 runLayoutTests
函数传递一些参数
snapshotStrategy
:默认情况下,当测试失败时,使用.failureOnly
,只捕获失败的快照并将其显示在测试报告中。如果您想为每个输入捕获每个视图的快照,可以传递.allTests
参数。randomStrategy
:默认情况下,使用random
生成真实随机值(这意味着两次不同的测试执行不会有相同的视图模型)。如果您想获得一致的输入,使用.consistently
或.replay
参数。maxTestsCount
:默认值为 100 个测试。如果需要,您可以降低此值。
返回值
runLayoutTests
函数接受一个闭包参数。此闭包返回一个 ViewAssertionResult
。此结果包含两个可选值:一个 view
和一个 error
。如果存在,view
会被用来截图并显示在测试报告中。如果返回了 error
,则测试失败。
快照测试
您可以使用相同的技术创建快照测试,并断言在开发过程中视图的外观不会改变。为此,导入快照测试库(在示例中我们使用 SnapshotTesting),并使用具有 .consistently
随机策略的 runLayoutTests
函数来生成始终相同的随机值。
func testExampleViewSavedScreenshots() {
runLayoutTests(
named: "ExampleView Saved Screenshots",
randomStrategy: .consistent, // mandatory to have the same screenshots every time
maxTestsCount: 5 // we only want 5 screenshots
) { (viewModel: ExampleViewModel) in
// same code as before
let view: ExampleView = ExampleView.fromNib(named: "ExampleViewValid")
view.backgroundColor = UIColor.white
view.frame = CGRect(x: 0, y: 0, width: 320.0, height: 150.0)
view.configure(with: viewModel)
view.setNeedsLayout()
view.layoutIfNeeded()
// Snapshot the view (depends of the library used)
assertSnapshot(
matching: view,
as: .image
)
// no layout assertions, we just check the generated screenshots
return .success // we don't pass the view here because we don't care
}
}
内置生成器
您可以在其中找到一些有用的预定义生成器,您可以用它来创建随机输入。
Gen<Character>
:.upper
、.lower
、.numeric
、.special
、.hexDigits
Gen<String>
:.word
、.words
、.sentences
、email
(单词和句子使用 lorem ipsum 内容构建)Gen<UIColor>
:.color
Gen<UIImage>
:.image(min:max:)
请随意利用 Gen
类型来创建自己的生成器。您可以在 SwiftCheck 页面找到所有文档。
需求
- iOS 9.0+
- Swift 5.1
安装
CocoaPods
CocoaPods 是 Cocoa 项目的依赖管理器。您可以使用以下命令安装它
$ gem install cocoapods
要使用 CocoaPods 将 ADLayoutTest 集成到您的 Xcode 项目中,请在您的 Podfile
中指定它
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '12.0'
use_frameworks!
target '<Your Target Name>' do
pod 'ADLayoutTest'
end
然后,运行以下命令
$ pod install
通讯
- 如果您 需要帮助,请使用 Twitter。
- 如果您想 提出一般性问题,请使用 Twitter。
- 如果您想 申请工作,请访问 https://careers.fabernovel.com/。
- 如果您 发现了错误,请打开一个 issue。
- 如果您 有功能请求,请打开一个 issue。
- 如果您 想做出贡献,提交一个 pull request。
鸣谢
ADLayoutTest 由 Fabernovel 拥有和维护。您可以在 @FabernovelTech 上关注我们。
许可证
ADLayoutTest 在MIT许可证下发布。更多信息请参阅LICENSE。