Проведите пальцем влево в модульном тестировании вручную, чтобы вызвать обработчик для тестирования в Swift

#ios #swift #uitableview #handler

#iOS #swift #uitableview #обработчик

Вопрос:

Я модульно тестирую 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
    }
  

Ответ №1:

Вы можете использовать тесты XCUITest, подобные этому:

 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 Recorder. Подробнее:

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

Вот пример, который я записал с помощью swipe:

https://youtu.be/lHafMlIcoCY

Комментарии:

1. Да, этот ответ не то, что я ищу, но я придержу этот ответ, если не смогу найти ответ на свой вопрос. Мой вопрос в том, можем ли мы протестировать swipe в модульном тестировании (не в тестировании пользовательского интерфейса)? Я имею в виду сам обработчик. Спасибо за ваш вклад!

2. Я не думаю, что вы можете. Вы могли бы поместить действие, вызываемое жестом swipe, в метод и вызвать его с помощью модульного теста, но это не совсем одно и то же.

Ответ №2:

Как сказал Дэвид С., я не верю, что есть способ запустить действие прокрутки в модульном тестировании. Однако, поскольку вы также спрашивали о вызове обработчика и тестировании его действий, я отвечу. Что касается тестирования 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 если не удастся удалить заметку? Вы всегда хотите возвращаться true из обработчика завершения, даже если удаление заметки не удалось? В первом случае вы можете рассмотреть возможность внедрения в NoteManager объект вместо простого вызова экземпляра singleton из вашего кода. Таким образом, вы можете проверить, происходит ли удаление при вызове этого обработчика.

Бонус:

Чтобы узнать ответ на первый вопрос (правильно ли настроены действия), мы можем внедрить объект, который будет предоставлять UIContextualAction в объект, который их использует (возможно, модель представления, если вы используете 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)
        }
    }
}
  

Теперь мы можем внедрить это в объект, которому требуются эти действия, и мы можем проверить, что они настроены правильно (как только мы вызовем действие, которое запускает настройку конфигурации swipe).