查看控制器TDD

col*_*fet 11 tdd unit-testing uiviewcontroller ios swift

我试图在我的项目中添加一些单元测试来测试视图控制器.但是我似乎遇到了看似简单的问题.我已经创建了一个示例项目,我将参考该项目.https://github.com/pangers/ViewControllerTesting

该示例包含一个UINavigationController作为初始视图控制器.UINavigationController的根视图控制器是FirstViewController.FirstViewController上有一个按钮,它可以切换到SecondViewController.在SecondViewController中有一个空文本字段.

我想添加的两个测试是:
1)FirstViewController中的检查按钮标题是"下一屏幕".
2)检查SecondViewController中的文本字段是否为空,"".

我听说有关将swift文件添加到主目标并且测试目标不是好习惯的报告.但是,最好在公共测试中创建您想要访问的任何内容,并将主目标导入测试中.这就是我所做的.(我还将主要目标的"定义模块"设置为YES,因为这是我在几篇文章中所读到的).

在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()
}
Run Code Online (Sandbox Code Playgroud)

我添加了测试:

func testCheckButtonHasTextNextScreen() {
    XCTAssertEqual(viewController.button.currentTitle!, "Next Screen", "Button should say Next Screen")
}
Run Code Online (Sandbox Code Playgroud)

同样,对于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()
}
Run Code Online (Sandbox Code Playgroud)

而且测试:

func testTextFieldIsBlank() {
    XCTAssertEqual(secondViewController.textField.text, "", "Nothing in textfield")
}
Run Code Online (Sandbox Code Playgroud)

他们都失败了,我不太清楚为什么.我怀疑我实例化视图控制器的方式不正确.实例化视图控制器的最佳方法是使用故事板(就像它在现实生活中运行一样)?或者可以通过以下方式实例化:

var viewController = FirstViewController()
Run Code Online (Sandbox Code Playgroud)

你们在swift中使用TDD和视图控制器的经历是什么?

我正在使用Swift和XCode 6.1.1.

提前致谢.

解决了

在考虑了modocache和Mike Taverne的答案后,我找到了解决方案,并且我已经学到了一些我将在下面写下的内容.

1)我做了一些我想测试公共的class/method/variable.我不需要将swift文件添加到测试目标.

2)我只需要为"Main"目标设置"Defines Module"(而不是"Test"目标或整个项目)

3)实例化storyboard时,bundle应设置为nil而不是NSBundle(forClass:self.dynamicType),否则测试将失败.

4)正如modocache所说,最好给你的视图控制器一个StoryboardID并像这样实例化它们:

viewController = storyboard.instantiateViewControllerWithIdentifier("FirstViewController") as FirstViewController
Run Code Online (Sandbox Code Playgroud)

但是,像这样实例化视图控制器只能实例化视图控制器,而不是它可能嵌入的任何导航控制器.这意味着,尝试做

XCTAssertFalse(viewController.navigationController!.navigationBarHidden, "Bar should show by default")
Run Code Online (Sandbox Code Playgroud)

将导致零例外.我证实了这一点

XCTAssertNil(viewController.navigationController?, "navigation controller doesn't exist")
Run Code Online (Sandbox Code Playgroud)

这导致了一次成功的测试.

由于我想检查FirstViewController中导航栏的状态,您必须像这样实例化视图控制器:

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

现在进行测试

XCTAssertFalse(viewController.navigationController!.navigationBarHidden, "nav bar should be showing by default")
Run Code Online (Sandbox Code Playgroud)

导致测试成功.

5)让_ = viewController.view确实触发了由测试确认的viewDidLoad()

6)let _ = viewController.view不会触发viewWillAppear(),之后我也会假设任何东西.需要手动调用viewController.viewWillAppear(false/true)来触发它(由测试确认).

希望这对人们有所帮助.如果有人想玩它,我会将更新的项目推送到GitHub(上面的链接).

更新#2

完成上述所有操作后,我仍然无法弄清楚如何从第一个视图控制器转换到第二个视图控制器(这样我可以在SecondViewControllerTests.swift中测试导航栏属性).我试过了

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
Run Code Online (Sandbox Code Playgroud)

这导致了一个错误.

我也试过了

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
Run Code Online (Sandbox Code Playgroud)

这没用.

我最终试过了

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)
Run Code Online (Sandbox Code Playgroud)

这与我的测试完美配合(允许我访问导航栏属性)!

mod*_*che 8

我同意@ MikeTaverne的回答:我更喜欢访问-[UIViewController view]以触​​发-[UIViewController viewDidLoad],而不是直接调用它.FirstViewController一旦你使用它,看看测试失败是否消失:

viewController = navigationController.topViewController as FirstViewController
let _  = viewController.view
Run Code Online (Sandbox Code Playgroud)

我还建议在故事板中提供两个视图控制器标识符.这将允许您直接实例化它们,而无需通过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
}
Run Code Online (Sandbox Code Playgroud)

UIViewController有关详细信息,请查看我在布鲁克林斯威夫特测试的演讲:https://vimeo.com/115671189#t=37m50s(我的演讲开始于37'50"左右).

  • 为了使您的代码更优雅,您还可以声明视图,如:XCTAssertNotNil(secondViewController.view).它还将触发生命周期流程. (2认同)