在单元测试中手动向左滑动以调用处理程序以在 Swift 中进行测试

Swipe left in unit test manually to call handler to test in Swift

我正在对 tableview 进行单元测试,我需要逻辑滑动吗? UITableView 中的一行。是否可以在单元测试中执行滑动以调用处理程序?我查看了 UITableViewDelegate,但没有滑动操作(didSelectRowAt 在那里并在单元测试中进行了测试)。

func createDeleteHandler(tableView : UITableView, indexPath : IndexPath) -> UIContextualAction.Handler {
        let deleteHandler =  { (ac:UIContextualAction, view:UIView, success:(Bool) -> Void) in
            let noteToBeDeleted = self.notes[indexPath.row]
            NoteManager.shared.deleteNote(note: noteToBeDeleted)
            self.notes.remove(at: indexPath.row)
            tableView.deleteRows(at: [indexPath], with: .fade)
            success(true)
        }

        return deleteHandler
    }

你可以使用XCUI这样测试测试:

import XCTest

class MoreUITests: XCTestCase {

    override func setUp() {
        continueAfterFailure = false
        XCUIApplication().launch()
    }

    func testUI() {

        let app = XCUIApplication()
        let tablesQuery = app.tables
        let addButton = app.navigationBars["Master"].buttons["Add"]
        let masterButton = app.navigationBars["Detail"].buttons["Master"]
        addButton.tap()  // adds Item-0
        addButton.tap()  // adds Item-1
        addButton.tap()  // adds Item-2
        addButton.tap()  // adds Item-3

        tablesQuery.staticTexts["Item-1"].tap()

        // Go back
        masterButton.tap()

        // Swipe Left on item-2
        tablesQuery.staticTexts["Item-2"].swipeLeft()

    }
}

最简单的方法是使用 Xcode UI 记录器记录它们。更多详情:

https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/testing_with_xcode/chapters/09-ui_testing.html

这是我用滑动记录的示例:

https://youtu.be/lHafMlIcoCY

正如 David S. 所说,我认为没有办法在单元测试中触发滑动操作。但是,由于您还询问了有关调用处理程序和测试其操作的问题,我会做出回应。关于为每个 table 视图单元测试 UIContextualAction(或操作),我们想知道:是否正确设置了所有属性(标题、图像等)的操作,我们是否调用他们的处理程序在适当的时间(当某些相关操作完成时)?

我会先回答第二个问题,因为那是你提出的问题,然后最后回答第一个问题作为奖励。要测试您发布的代码片段,请在单元测试中调用该函数,将结果存储在 属性 中,创建类型为 (Bool) -> Void 的闭包,然后调用上下文操作的 completionHandler 闭包您创建的闭包作为其参数:

func testCompletionHandlerCalled() {
    let completionHandlerCalledExpectation = expectation(description: "Completion handler called)"

    // Do whatever it is you need to do make sure your table view data is loaded
    // and the view is added properly to your view hierarchy
    let tableView = MyTableView()
    let sut = MyTableViewDelegateObject() // In your case, the object from your code snippet
    let indexPath = IndexPath(row: 0, section: 1) // Whichever index path you want to test
    let deleteHandler = sut.createDeleteHandler(tableView: tableView, indexPath: indexPath)

    let stubCompletion: (Bool) -> Void = { success in
        XCTAssertTrue(success)
        completionHandlerCalledExpectation.fulfill()
    }

    // You could structure your code different to return the action and not just the handler,
    // which you'd then pass in here
    deleteHandler(UIContextualAction(), UIView(), stubCompletion)

    waitForExpectations(timeout: 0.001)
}

这个测试本身并不是特别有用,因为它并没有真正测试用户点击删除按钮时发生的事情的实质:删除笔记的逻辑和操作。因此,关于您的代码,您还想问自己一些其他问题:如果 NoteManager 无法删除笔记会怎样?你是否总是希望从完成处理程序中 return true,即使笔记删除不成功?对于前者,您可能需要考虑注入 NoteManager object 而不是仅从您的代码中调用单例实例。这样,您可以测试调用此处理程序时是否正在发生删除。

奖金:

为了知道第一个问题的答案(是否正确设置了操作),我们可以注入一个 object,它将提供一个 UIContextualAction 到 object 使用它们(如果您使用 MVVM,则可能是视图模型;如果您使用 MVC,则可能是视图控制器)。枚举的使用让我们在设置每个操作时围绕它进行一些输入,并有助于提供一些关于它的使用的上下文。像这样:

enum MyTableViewCellActionTypes {
    case edit
    case delete
}

protocol UIContextualActionProviderType {
    func action(for type: FoodItemActionType, handler: @escaping UIContextualAction.Handler) -> UIContextualAction
}

struct NotesContextualActionsProvider: UIContextualActionProviderType {
    func action(for type: FoodItemActionType, handler: @escaping UIContextualAction.Handler) -> UIContextualAction {
        switch type {
        case .edit: return UIContextualAction(style: .normal, title: "Edit", handler: handler)
        case .delete: return UIContextualAction(style: .destructive, title: "Delete", handler: handler)
        }
    }
}

现在,我们可以将其注入到需要这些操作的 object 中,并且我们可以测试它们是否设置正确(一旦我们调用触发滑动配置设置的操作)。