SwityKit - 一种使单元测试编写变得容易的基础设施,旨在增加覆盖范围
SwityKit
SwityKit 是一个基础设施,可以轻松编写单元测试,并旨在增加覆盖范围。
与 SwityTestGenerator 一起使用。这个 xcode 源代码编辑器扩展有助于模拟生成。
好处
🚀 生成对调试日志的断言
🚀 将测试运行时间缩短 ~40%
🚀 它可以防止我们编写不完整的测试
🚀 更少的模拟代码
🚀 测试它是否以正确的顺序调用。
安装
Swift Package Manager
要使用 Swift Package Manager 将 SwityKit 集成到 Xcode 项目中,请将其添加到 :Package.swift
dependencies: [
.package(url: "https://github.com/aytugsevgi/SwityKit", from: "1.0.5")
]
您需要为测试目标添加 to。同时从主目标中删除。Build Phases -> Link Binary With Libraries
例
模拟
final class MockHomeViewController: HomeViewControllerInterface, MockAssertable {
typealias MockIdentifier = MockHomeViewControllerElements
var invokedList: [MockHomeViewControllerElements] = []
var stubbedPreferredTabBarVisibility: TabBarVisibility!
var preferredTabBarVisibility: TabBarVisibility {
invokedList.append(.preferredTabBarVisibility)
return stubbedPreferredTabBarVisibility
}
func viewDidLoad() {
invokedList.append(.viewDidLoad)
}
func scroll(to indexPath: IndexPath) {
invokedList.append(.scroll(to: indexPath))
}
}
enum MockHomeViewControllerElements: MockEquatable {
case preferredTabBarVisibility
case viewDidLoad()
case scroll(to: IndexPath)
}
Test
final class HomeViewPresenterTests: XCTestCase, BaseTestCaseInterface {
var mocks: [MockAssertable] { [view, delegate] }
var view: MockHomeViewController!
var delegate: MockHomeDelegate!
var presenter: HomeViewPresenter!
override func setUp() {
super.setUp()
view = .init()
delegate = .init()
presenter = .init(view: view)
}
override func tearDown() {
super.tearDown()
view.assertions("view")
delegate.assertions("delegate")
view = nil
delegate = nil
presenter = nil
}
func test_viewDidLoad_InvokesRequiredMethods() {
invokedNothing()
presenter.viewDidLoad()
view.assertInvokes([.preferredTabBarVisibility,
.scroll(to: .init(item: 0, section: 1))])
invokedNothing(excepts: [view])
}
}
Debug Log (Generation of assertions)
- Manipulating test generation. If you want to change the output of a type, you can write extension with conform .
CustomStringConvertable
extension User: CustomStringConvertable {
public var description: String {
".init(name: \"\(self.name)\", age: \"\(self.age)\")"
}
}
- If that type already conforms to , you just need to override it.
CustomStringConvertable
extension NSAttributedString {
public override var description: String {
".init(string: \"\(self.string)\")"
}
}
Deep Dive
MockAssertable
This protocol must conform to mock classes. With this protocol, the mock class has an array called . The element of this array is the typealias that is expected to be defined with .invokedList
MockIdentifier
final class MockHomeViewController: HomeViewControllerInterface, MockAssertable {
typealias MockIdentifier = MockHomeViewControllerElements
var invokedList: [MockHomeViewControllerElements] = []
.
.
.
MockIdentifier
type must be enum with conformed . Like example above.MockEquatable
MockHomeViewControllerElements
enum MockHomeViewControllerElements: MockEquatable {
...
}
MockAssertable
also has helpful public APIs.
assertInvokes()
: Tests that nothing was invoked from mock.assertInvokes(_ givenInvokes: [MockIdentifier])
: Tests whether the elements in the array have been invoked. If it is missing or extra, it will give an error. It also warns you if the array is in the wrong order.assertions(name: String)
: Generate assertions to debug log according to array. If you call on it’s generate correctly after run each test func.invokedList
override func tearDown()
tearDown()
: Deletes all elements of the invokedList array. It continues as nothing was invoked.
MockEquatable
MockEquatable
allows us to assert. It allows to get the String describing of the enum case in which it is conformed. Then it looks at the equality of these 2 strings when doing the comparison.
BaseTestCaseInterface
This protocol must conform to test classes. Requests mocks from test class. Like,
final class HomeViewPresenterTests: XCTestCase, BaseTestCaseInterface {
var mocks: [MockAssertable] { [view, delegate] }
.
.
.
APIs it provides;
tearDownMocks()
: Empties the invokedList array of all mocks. It continues as if nothing was invoked.invokedNothing(excepts: [BaseMockAssertable] = .empty)
: Shortcut assertion. Tests that the given in the test class’s mocks array is not invoked at all. Does not check mocks given to .excepts