Swift利用面向协议的编程进行单元测试

示例

面向协议的编程是一种有用的工具,可以轻松地为我们的代码编写更好的单元测试。

假设我们要测试依赖于ViewModel类的UIViewController。

生产代码所需的步骤为:

  1. 定义一个协议,该协议公开ViewView类的公共接口,以及UIViewController所需的所有属性和方法。

  2. 实现符合该协议的真实ViewModel类。

  3. 使用依赖注入技术让视图控制器使用我们想要的实现,将其作为协议而不是具体实例传递。

protocol ViewModelType {
   var title : String {get}
   func confirm()
}

class ViewModel : ViewModelType {
   let title : String

   init(title: String) {
      self.title= title
   }
   func confirm() { ... }
}

class ViewController : UIViewController {
   // 我们将viewModel属性声明为符合协议的对象
   // 因此我们可以毫不费力地交换实现。
   var viewModel : ViewModelType! 
   @IBOutlet var titleLabel : UILabel!

   override func viewDidLoad() {
       super.viewDidLoad()
      titleLabel.text= viewModel.title
   }

   @IBAction func didTapOnButton(sender: UIButton) {
       viewModel.confirm()
   }
}

// 使用DI,我们可以设置视图控制器并分配视图模型。
// 视图控制器不知道视图模型的具体类, 
// 但仅依赖协议上声明的接口。
let viewController = //...实例化视图控制器
viewController.viewModel = ViewModel(title: "MyTitle")

然后,在单元测试中:

  1. 实现符合相同协议的模拟ViewModel

  2. 使用依赖项注入而不是真实实例将其传递给接受测试的UIViewController。

  3. 测试!

class FakeViewModel : ViewModelType {
   let title : String = "FakeTitle"

   var didConfirm = false
   func confirm() {
       didConfirm = true
   }
}

class ViewControllerTest : XCTestCase {
    var sut : ViewController!
    var viewModel : FakeViewModel!

    override func setUp() {
        super.setUp()

        viewModel = FakeViewModel()
        sut = // ...为视图控制器初始化
       sut.viewModel= viewModel

        XCTAssertNotNil(self.sut.view) // 需要触发视图加载
    } 

    func testTitleLabel() {
        XCTAssertEqual(self.sut.titleLabel.text, "FakeTitle")
    }

    func testTapOnButton() {
        sut.didTapOnButton(UIButton())
        XCTAssertTrue(self.viewModel.didConfirm)
    }
}