Вид контроллера TDD

Я пытаюсь добавить некоторые модульные тесты в свой проект для тестирования контроллеров представления. Однако у меня, похоже, проблемы с кажущимися простыми вещами. Я создал образец проекта, на который я буду ссылаться. https://github.com/pangers/ViewControllerTesting

образец содержит UINavigationController в качестве контроллера начального вида. Контроллер корневого представления UINavigationController является FirstViewController. На FirstViewController есть кнопка, которая переход к SecondViewController. В SecondViewController есть пустое текстовое поле.

два теста, которые я пытаюсь добавить:
1) проверьте название кнопки в FirstViewController - "следующий экран".
2) Проверьте textfield в SecondViewController пуст,"".

Я слышал отчеты о добавлении ваших файлов swift как к основной цели, так и к тестовой цели не является хорошей практикой. Но лучше сделать все, что вы хотите получить в своих тестах, общедоступным и импортируйте основную цель в тесты. Вот что я сделал. (Я также установил "определяет модуль" для основной цели да, поскольку это то, что я прочитал в нескольких статьях).

в FirstViewControllerTests я создал экземпляр первого контроллера вида со следующим:

var viewController: FirstViewController!

override func setUp() {
    let storyboard = UIStoryboard(name: "Main", bundle: NSBundle(forClass: self.dynamicType))
    let navigationController = storyboard.instantiateInitialViewController() as UINavigationController
    viewController = navigationController.topViewController as FirstViewController
    viewController.viewDidLoad()
}

и я добавил тест:

func testCheckButtonHasTextNextScreen() {
    XCTAssertEqual(viewController.button.currentTitle!, "Next Screen", "Button should say Next Screen")
}

аналогично, для SecondViewControllerTest я установил его с помощью:

var secondViewController:SecondViewController!

override func setUp() {
    let storyboard = UIStoryboard(name: "Main", bundle: NSBundle(forClass: self.dynamicType))
    let navigationController = storyboard.instantiateInitialViewController() as UINavigationController
    let firstviewController = navigationController.topViewController as FirstViewController
    firstviewController.performSegueWithIdentifier("FirstToSecond", sender: nil)
    secondViewController = navigationController.topViewController as SecondViewController
    secondViewController.viewDidLoad()
}

и тест:

func testTextFieldIsBlank() {
    XCTAssertEqual(secondViewController.textField.text, "", "Nothing in textfield")
}

они оба терпят неудачу и я не слишком уверен, почему. Я подозреваю, что способ, которым я создаю экземпляры контроллеров представления, неверен. Лучший способ создать экземпляр контроллеров представления-использовать раскадровку (так же, как если бы она работала в реальной жизни)? Или допустимо создание экземпляра с помощью:

var viewController = FirstViewController()

какой у вас опыт работы с TDD и контроллерами просмотра в swift?

я использую Swift с XCode 6.1.1.

спасибо заранее.

решить

хорошо после рассмотрения ответов от modocache и Mike Taverne, я нашел свое решение, и я узнал несколько вещей, которые я запишу ниже.

1) я сделал все, что класс/метод/переменная, которые я хочу проверить публично. Мне не нужно добавлять файлы swift в тестовую цель.

2) мне нужно было только установить "определяет модуль "для" основной " цели (в отличие от "тестовая" цель или весь проект)

3) при создании экземпляра раскадровки пакет должен быть установлен в nil, а не NSBundle(forClass: self.dynamicType), в противном случае тесты завершатся неудачей.

4) Как заявил modocache, хорошо дать вашему контроллеру view StoryboardID и создать их так:

viewController = storyboard.instantiateViewControllerWithIdentifier("FirstViewController") as FirstViewController

однако создание экземпляра контроллера вида, как это только создает экземпляр контроллера вида в одиночку, а не каких-либо навигационных контроллеров что он может быть встроен. Это означает, что попытка сделать

XCTAssertFalse(viewController.navigationController!.navigationBarHidden, "Bar should show by default")

приведет к нулевому исключению. Я подтвердил это с

XCTAssertNil(viewController.navigationController?, "navigation controller doesn't exist")

что привело к успешному тесту.

поскольку я хотел проверить состояние панели навигации в FirstViewController, вы должны создать экземпляр контроллера вида следующим образом:

let storyboard = UIStoryboard(name: "Main", bundle: nil)
let navigationController = storyboard.instantiateInitialViewController() as UINavigationController
viewController = navigationController.topViewController as FirstViewController

теперь выполнение теста

XCTAssertFalse(viewController.navigationController!.navigationBarHidden, "nav bar should be showing by default")

результаты успешного испытания.

5) пусть _ = файл ViewController.view действительно запускает viewDidLoad (), что было подтверждено тестом

6) пусть _ = viewController.view не запускает viewWillAppear (), и я предполагаю что-нибудь после этого. файл ViewController.viewWillAppear (false/true) необходимо вызвать вручную, чтобы вызвать его (подтверждено тестом).

надеюсь, это поможет людям. Я подтолкну обновленный проект к GitHub (ссылка выше), если кто-то захочет поиграть с ним.

обновление #2

после всего вышесказанного я все еще не мог понять, как перейти от первого контроллера вида ко второму контроллеру вида (чтобы я мог проверить свойства навигационной панели в SecondViewControllerTests.стремительный.) Я пытался!--16-->

let storyboard = UIStoryboard(name: "Main", bundle: nil)
let nc = storyboard.instantiateInitialViewController() as UINavigationController
let firstVC = nc.topViewController as FirstViewController
firstVC.performSegueWithIdentifier("FirstToSecond", sender: nil)
secondVC = nc.topViewController as SecondViewController

, которое вызвало ошибку.

я тоже пробовал

let storyboard = UIStoryboard(name: "Main", bundle: nil)
let nc = storyboard.instantiateInitialViewController() as UINavigationController
let firstVC = nc.topViewController as FirstViewController
firstVC.toSecondVCButton.sendActionsForControlEvents(UIControlEvents.TouchUpInside)
secondVC = nc.topViewController as SecondViewController

что не сработало.

в конце концов я попытался

let storyboard = UIStoryboard(name: "Main", bundle: nil)
let nc = storyboard.instantiateInitialViewController() as UINavigationController
vc = storyboard.instantiateViewControllerWithIdentifier("Second") as SecondViewController
nc.pushViewController(vc, animated: false)
let _ = vc.view
vc.viewWillAppear(false)

что отлично работал с моими тестами (позволил мне получить доступ к свойствам навигационной панели)!

2 ответов


Я согласен с ответом @Miketaverne'S: Я предпочитаю доступ -[UIViewController view] для того, чтобы вызвать -[UIViewController viewDidLoad], а не называть его напрямую. Смотрите, если тестовые сбои для FirstViewController уходите, как только вы используете это вместо этого:

viewController = navigationController.topViewController as FirstViewController
let _  = viewController.view

Я бы также рекомендовал предоставить идентификаторы обоих контроллеров просмотра в вашей раскадровке. Это позволит вам создавать их непосредственно, без доступа к ним через UINavigationController:

var secondViewController: SecondViewController!

override func setUp() {
    let storyboard = UIStoryboard(name: "Main", bundle: NSBundle(forClass: self.dynamicType))
    secondViewController = storyboard.instantiateViewControllerWithIdentifier("SecondViewController")
        as SecondViewController
    let _ = secondViewController.view
}

проверьте мой разговор о тестировании UIViewController в Бруклин Свифт для подробности:https://vimeo.com/115671189#t=37m50s (моя презентация начинается с отметки 37'50").


недавно я начал модульное тестирование контроллеров представления, и это создает некоторые уникальные проблемы.

одна задача - загрузить представление. Глядя на вашу настройку для FirstViewController, вы пытаетесь сделать это с помощью viewController.viewDidLoad ().

мое предложение-заменить эту строку следующим:

пусть dummy = viewController.вид

доступа .свойство view заставит представление загружаться. Это спровоцирует .в метод viewDidLoad ваш ViewController, поэтому не вызывайте этот метод явно в своем тесте.

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

в стороне, я нахожу лучший способ проверить контроллеры представления, чтобы переместить как можно больше кода из контроллеров представления в другие классы, которые легче протестировать.

Если ваш контроллер вида определен в раскадровка, то вам нужно создать его таким образом, чтобы ваши розетки были настроены правильно. Попытка инициализировать его как обычный класс не сработает.