【问题标题】:Can't cast custom UIViewController from UIStoryboard in XCTest无法从 XCTest 中的 UIStoryboard 投射自定义 UIViewController
【发布时间】:2014-10-10 06:52:39
【问题描述】:

在 Swift Xcode6-Beta5 中,我正在尝试对我的“ViewController”进行单元测试,这不是一个很有创意的名称。

通过查看其他回复,我认为我的“测试”目标配置不正确。

失败的测试代码:

  func testItShouldLoadFromStoryboard() {

    var storyBoard: UIStoryboard?
    var anyVC: AnyObject?
    var viewController: ViewController?
    var uiViewController: UIViewController?

    storyBoard = UIStoryboard(name:"Main", bundle: nil)
    XCTAssert(storyBoard != nil, "Test Not Configured Properly")

    anyVC = storyBoard?.instantiateInitialViewController()
    viewController = anyVC  as? ViewController
    // Failing Assertion 
    XCTAssert(viewController != nil, "Test Not Configured Properly")

    uiViewController = anyVC as? UIViewController
    XCTAssert(uiViewController != nil, "Test Not Configured Properly")

  }

我可以用以下几行强制转换:

anyVC = storyBoard?.instantiateInitialViewController()
viewController = (anyVC != nil) ? (anyVC  as ViewController) : nil

但这导致了以下崩溃:

libswiftCore.dylib`swift_dynamicCastClassUnconditional:
0x10724f5a0:  pushq  %rbp
0x10724f5a1:  movq   %rsp, %rbp
0x10724f5a4:  pushq  %r14
0x10724f5a6:  pushq  %rbx
0x10724f5a7:  movq   %rsi, %rbx
0x10724f5aa:  movq   %rdi, %r14
0x10724f5ad:  testq  %r14, %r14
0x10724f5b0:  je     0x10724f5de               ; swift_dynamicCastClassUnconditional + 62
0x10724f5b2:  movabsq $-0x7fffffffffffffff, %rax
0x10724f5bc:  andq   %r14, %rax
0x10724f5bf:  jne    0x10724f5de               ; swift_dynamicCastClassUnconditional + 62
0x10724f5c1:  movq   %r14, %rdi
0x10724f5c4:  callq  0x107279a6e               ; symbol stub for: object_getClass
0x10724f5c9:  nopl   (%rax)
0x10724f5d0:  cmpq   %rbx, %rax
0x10724f5d3:  je     0x10724f5ed               ; swift_dynamicCastClassUnconditional + 77
0x10724f5d5:  movq   0x8(%rax), %rax
0x10724f5d9:  testq  %rax, %rax
0x10724f5dc:  jne    0x10724f5d0               ; swift_dynamicCastClassUnconditional + 48
0x10724f5de:  leaq   0x3364d(%rip), %rax       ; "Swift dynamic cast failed"
0x10724f5e5:  movq   %rax, 0xa456c(%rip)       ; gCRAnnotations + 8
0x10724f5ec:  int3   
0x10724f5ed:  movq   %r14, %rax
0x10724f5f0:  popq   %rbx
0x10724f5f1:  popq   %r14
0x10724f5f3:  popq   %rbp
0x10724f5f4:  retq   
0x10724f5f5:  nopw   %cs:(%rax,%rax)

我还成功地直接实例化了 ViewController,但这并没有进行 IBOutlet 处理,这是我测试的目的之一,以确保我不会通过重命名、删除连接中的连接来破坏链接故事板编辑器,或者我发现的许多其他破坏方式......

编辑---- 我从一个新项目开始,选择了 iOS 应用程序 -> 单视图应用程序模板。

将 testExample 替换为如下所示的代码,将 ViewController 添加到测试目标中。类似的结果。这个模板有一个 ViewController 类型的视图控制器,故事板中没有其他内容。

  func testExample() {


      var storyBoard: UIStoryboard?
      var anyVC: AnyObject?
      var viewController: ViewController?

      storyBoard = UIStoryboard(name:"Main", bundle: nil)
      XCTAssert(storyBoard != nil, "Test Not Configured Properly")

      anyVC = storyBoard?.instantiateInitialViewController()
      viewController = anyVC as? ViewController
      XCTAssert(viewController != nil, "Test Not Configured Properly")

      // This is an example of a functional test case.
      XCTAssert(true, "Pass")

    }

在 anyVC 的值设置后的断点处的以下 lldb 输出:

(lldb) po anyVC
(instance_type = Builtin.RawPointer = 0x00007fe22c92a290 -> 0x000000010e20bd80 (void *)0x000000010e20bea0: OBJC_METACLASS_$__TtC22TestingViewControllers14ViewController)
 {
  instance_type = 0x00007fe22c92a290 -> 0x000000010e20bd80 (void *)0x000000010e20bea0: OBJC_METACLASS_$__TtC22TestingViewControllers14ViewController
} 

【问题讨论】:

  • 看起来anyVC 不是ViewController 类型,那么您确定故事板中的初始视图控制器实际上是该类型吗?确保 (1) 在身份检查器中检查初始视图控制器的类并 (2) 在 instantiateInitialViewController() 之后设置断点并查看 anyVC 的类型。使用您找到的信息编辑您的问题。
  • 我可能会抢先一步:rdar://18043164
  • 在我看来,问题在于storyboard中ViewController的类型是App.ViewController,与ViewController不同。请记住,swift 有模块。当您将 ViewController 链接到您的测试目标时,您有效地创建了该类的两个版本:App.ViewController 和 AppTests.ViewController,它们并不相同(它们不会正确转换)。我不知道如何从测试目标中的应用目标引用 Swift 类。不过,一定有办法。希望对您有所帮助。
  • instantiateInitialViewController 调用之后添加println(anyVC)println(ViewController()) 说明了区别。
  • @DerrickHathaway 感谢您指出这一点。我也有这个问题。我看到当我使用您的 println() 语句时,我得到了 Target.ViewController 这可能是问题的根源。

标签: swift xctest


【解决方案1】:

我想出了一个比公开所有内容更好的解决方案。您实际上只需要使用来自测试包的情节提要,而不是使用 nil(强制它来自主要目标的包)。

var storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: NSBundle(forClass: self.dynamicType))
vc = storyboard.instantiateViewControllerWithIdentifier("LoginVC") as LoginViewController
vc.loadView()

【讨论】:

  • 是的......这是一个更好的答案。但是请把答案的精髓放在这里...我认为它更适合StackOverflow风格... let storyBoard = UIStoryboard(name:"Main", bundle: NSBundle(forClass: self.dynamicType))
  • 你是对的。我之前很着急。我已经更新了答案以包含帖子的重要部分。
  • 在 Obj-C 中:---- [[UIStoryboard storyboardWithName:@"STORYBOARDNAME" bundle:[NSBundle mainBundle]] instantiateViewControllerWithIdentifier:@"IDENTIFIER"];
  • loadView() 是一种私有 API 方法,不被认为是良好的编码习惯,应用程序也可能会因为实现此方法而被苹果从应用商店拒绝。
  • @JacobKing, loadView() 并不是真正的“私人”。你是对的,你通常不应该在你的主要目标中调用它,但这是专门用于测试的。调用 loadView() 在测试中可能很有用,因为您可能希望在没有应用程序其余部分的上下文的情况下加载单个 ViewController
【解决方案2】:

如果你设置了storyboardID,那么你可以像这样投射它。

您还需要将视图控制器类重命名为例如此处的 MyAppViewController 让主故事板使用它而不是标准的视图控制器类,然后将故事板 ID 设置为例如 MyappVC

  var sut : MyAppViewController!

override func setUp() {
    super.setUp()

    // get a reference to storyboard and the correct
    // viewcontroller inside it. Remember to add
    // storyboardID in this case "MyappVC"


    let StoryBoard = UIStoryboard.init(name: "Main", bundle: nil)
    sut = StoryBoard.instantiateViewControllerWithIdentifier("Myapp VC")as!
    CalculatorViewController

    //trigger viewDidLoad()
    _ = sut.view

【讨论】:

    【解决方案3】:

    感谢@Derrik Hathaway 提供解决方案的关键。应用程序和测试目标是不同的模块。

    我的应用程序有一个不幸的名称TestingViewControllers 为了更正这个问题,我进行了以下更改并将 ViewController 重新设置为应用程序代码中的 public 类。

    在测试用例中添加了这一行:

    import TestingViewControllers
    

    把测试用例改成这样:

    func testExample() {
    
    
      var storyBoard: UIStoryboard?
      var anyVC: AnyObject?
      var viewController: TestingViewControllers.ViewController?
    
      storyBoard = UIStoryboard(name:"Main", bundle: nil)
      XCTAssert(storyBoard != nil, "Test Not Configured Properly")
    
      anyVC = storyBoard?.instantiateInitialViewController()
      viewController = anyVC as? TestingViewControllers.ViewController
      XCTAssert(viewController != nil, "Test Not Configured Properly")
    
      // This is an example of a functional test case.
      XCTAssert(true, "Pass")
    
    }
    

    测试用例通过

    【讨论】:

    • 我认为我们不应该将每个 ViewController 都声明为公共的,尽管此时我会采取任何可行的解决方案。
    • 您还必须将每个 IBOutlet 声明为公开的,以便测试您的 UI。
    猜你喜欢
    • 2012-09-29
    • 1970-01-01
    • 1970-01-01
    • 2016-03-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-09
    • 2019-08-17
    • 2018-11-30
    相关资源
    最近更新 更多