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

屏幕截图 2022-09-16 在 14 27 54

模拟

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)

Screen Shot 2022-09-16 at 00 40 25

  • 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 .invokedListMockIdentifier

final class MockHomeViewController: HomeViewControllerInterface, MockAssertable {
    typealias MockIdentifier = MockHomeViewControllerElements
    var invokedList: [MockHomeViewControllerElements] = []
    .
    .
    .

MockIdentifier type must be enum with conformed . Like example above.MockEquatableMockHomeViewControllerElements

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.invokedListoverride 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

GitHub

点击跳转