面向协议的编程是一种有用的工具,可以轻松地为我们的代码编写更好的单元测试。
假设我们要测试依赖于ViewModel类的UIViewController。
生产代码所需的步骤为:
定义一个协议,该协议公开ViewView类的公共接口,以及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 { // 我们将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")
然后,在单元测试中:
实现符合相同协议的模拟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 = // ...为视图控制器初始化 sut.viewModel= viewModel XCTAssertNotNil(self.sut.view) // 需要触发视图加载 } func testTitleLabel() { XCTAssertEqual(self.sut.titleLabel.text, "FakeTitle") } func testTapOnButton() { sut.didTapOnButton(UIButton()) XCTAssertTrue(self.viewModel.didConfirm) } }