利用面向协议的编程进行单元测试
面向协议编程是一个有用的工具,可以轻松地为我们的代码编写更好的单元测试。
假设我们想要测试一个依赖于 ViewModel 类的 UIViewController。
生产代码所需的步骤是:
- 定义一个公开 ViewModel 类的公共接口的协议,以及 UIViewController 所需的所有属性和方法。
- 实现真正的 ViewModel 类,符合该协议。
- 使用依赖注入技术让视图控制器使用我们想要的实现,将其作为协议而不是具体实例传递。
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 {
// We declare the viewModel property as an object conforming to the protocol
// so we can swap the implementations without any friction.
var viewModel : ViewModelType!
@IBOutlet var titleLabel : UILabel!
override func viewDidLoad() {
super.viewDidLoad()
titleLabel.text = viewModel.title
}
@IBAction func didTapOnButton(sender: UIButton) {
viewModel.confirm()
}
}
// With DI we setup the view controller and assign the view model.
// The view controller doesn't know the concrete class of the view model,
// but just relies on the declared interface on the protocol.
let viewController = //... Instantiate view controller
viewController.viewModel = ViewModel(title: "MyTitle")
然后,在单元测试:
- 实现符合相同协议的模拟 ViewModel
- 使用依赖注入将其传递给测试中的 UIViewController,而不是真实实例。
- 测试!
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 = // ... initialization for view controller
sut.viewModel = viewModel
XCTAssertNotNil(self.sut.view) // Needed to trigger view loading
}
func testTitleLabel() {
XCTAssertEqual(self.sut.titleLabel.text, "FakeTitle")
}
func testTapOnButton() {
sut.didTapOnButton(UIButton())
XCTAssertTrue(self.viewModel.didConfirm)
}
}