#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. Подробнее:
Вот пример, который я записал с помощью swipe:
Комментарии:
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).