但是以上的内容我都是带过,不会花太时间解释
我们的注意力会放在单元测试上。
这个是目前View的画面,目前还没有套用任何逻辑。
目标是让输入匡输入5个字元,就算是符合规范。
帐号与密码都符合规范,Login in 的按钮才可以按。
这是目前的view Controller,什麽东西都没有。
class LoginPageViewController:UIViewController{}
先在 LoadView 读取客制化的view 。
class LoginPageViewController:UIViewController{
var loginPageView:LoginPageView!
//MARK: - LoadView()
override func loadView() {
loginPageView = LoginPageView()
self.view = loginPageView
}
}
很好,已经读到画面了。
但是还是没办法有逻辑上的连动
我把binding的过程分开,当然你可以写在一起。
为了refacter方便我会分开写。
//MARK: - DataBinding()
func dataBinding(){
//observable
//vaild
//bind
}
}
这里我创建了两个推送事件序列,这两个推送的物件是 UITextField的text属性。
//MARK: - DataBinding()
func dataBinding(){
//observable
let usernameUITextFieldObservable = loginPageView.usernameTextField.rx.text.orEmpty
let passnameUITextFieldObservable = loginPageView.passwordTestField.rx.text.orEmpty
//vaild
let usernameVaild = usernameUITextFieldObservable
.map{ $0.count >= minimalUsernameLength}
let passwordVaild = passnameUITextFieldObservable
.map{ $0.count >= minimalPasswordLength}
let everythingVaild = Observable.combineLatest(usernameVaild,passwordVaild)
.map{ $0 && $1 }
//bind
}
这里有三道业务逻辑。
//MARK: - DataBinding()
func dataBinding(){
//observable
let usernameUITextFieldObservable = loginPageView.usernameTextField.rx.text.orEmpty
let passnameUITextFieldObservable = loginPageView.passwordTestField.rx.text.orEmpty
//vaild
let usernameVaild = usernameUITextFieldObservable
.map{ $0.count >= minimalUsernameLength}
let passwordVaild = passnameUITextFieldObservable
.map{ $0.count >= minimalPasswordLength}
let everythingVaild = Observable.combineLatest(usernameVaild,passwordVaild)
.map{ $0 && $1 }
//bind
usernameVaild.bind(to: loginPageView.usernameValidUILabel.rx.isHidden)
.disposed(by: disposeBag)
passwordVaild.bind(to: loginPageView.passwordValidUILabel.rx.isHidden)
.disposed(by: disposeBag)
everythingVaild.bind(to: loginPageView.loginButton.rx.isEnabled)
.disposed(by: disposeBag)
}
对应要做出反应的参数。
这边要注意 dispose 的回收机制。
有兴趣可以参考autoreleasepool,这是相同的回收机制。
//MARK: - DataBinding()
func dataBinding(){
//observable
let usernameUITextFieldObservable = loginPageView.usernameTextField.rx.text.orEmpty
let passnameUITextFieldObservable = loginPageView.passwordTestField.rx.text.orEmpty
//vaild
let usernameVaild = usernameUITextFieldObservable
.map{ $0.count <= minimalUsernameLength}
let passwordVaild = passnameUITextFieldObservable
.map{ $0.count <= minimalPasswordLength}
let everythingVaild = Observable.combineLatest(usernameVaild,passwordVaild)
.map{ $0 && $1 }
//bind
usernameVaild.bind(to: loginPageView.usernameValidUILabel.rx.isHidden)
.disposed(by: disposeBag)
usernameVaild.bind(to: loginPageView.passwordValidUILabel.rx.isHidden)
.disposed(by: disposeBag)
everythingVaild.bind(to: loginPageView.loginButton.rx.isEnabled)
.disposed(by: disposeBag)
}
以上我们已经完成了资料绑定,已经可以做互动了。
我们从一个测试类别开始
class MVCLearnTests: XCTestCase {}
我们配置好 sut <-- 受测试的物件
class MVCLearnTests: XCTestCase {
var sut : LoginPageViewController!
override func setUp() {
super.setUp()
sut = LoginPageViewController()
}
override func tearDown() {
super.tearDown()
sut = nil
}
}
建议善用 setUp 与 tearUp 的回收机制。避免因为没有清除影响其他测试。
延伸阅读: zombie objects 。
基本的配置完成後,可以开始写测试的函式了。
func testLoginPageViewController_whenUsernameIsVaild_usernameVaildUIlabelisEnable(){
//given
//when
//then
}
先写好三个测试流程的步骤:
这是为了方便建制这个 Test的流程。
Given 在特定的条件下
When 当某个行为发生时
Then 预期要发生的结果
延伸阅读:命名规范
func testLoginPageViewController_whenUsernameIsVaild_usernameVaildUIlabelisEnable(){
//given
let text = "12345"
//when
sut.loginPageView.usernameTextField.text = text
//then
let isEnabled = sut.loginPageView.usernameTextField.isEnabled
XCTAssertEqual(isEnabled, true)
}
测试逻辑写完後 command + u 测试看看。
结果发生问题了,这是为什麽呢?
因此我们要实例化UI的物件。
我们是在 LoadView() 实例化物件的。所以我们让 sut 执行 LoadView()
override func setUp() {
super.setUp()
sut = LoginPageViewController()
sut.loadView()
}
command + u 再测试一次。
测试成功了
我们从一个空白的class开始。
class LoginPageViewModel{}
然後把刚刚 vaild 的片段(业务逻辑)贴过来。
然後稍作改写一下 viewModel 就成形了
class LoginPageViewModel{
var usernameVaild:Observable<Bool>
var passwordVaild:Observable<Bool>
var everythingVaild:Observable<Bool>
init (username:Observable<String>,password:Observable<String>){
//vaild
usernameVaild = username
.map{ $0.count >= minimalUsernameLength}
passwordVaild = password
.map{ $0.count >= minimalPasswordLength}
everythingVaild = Observable.combineLatest(usernameVaild,passwordVaild)
.map{ $0 && $1 }
}
}
接下来把业务逻辑抽离。
import UIKit
import RxCocoa
import RxSwift
let minimalUsernameLength = 5
let minimalPasswordLength = 5
class LoginPageViewController:UIViewController{
var loginPageView:LoginPageView!
var disposeBag:DisposeBag!
var viewModel : LoginPageViewModel!
//MARK: - LoadView()
override func loadView() {
loginPageView = LoginPageView()
self.view = loginPageView
}
//MARK: - ViewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
disposeBag = DisposeBag()
dataBinding()
}
//MARK: - DataBinding()
func dataBinding(){
//observable
viewModel = LoginPageViewModel(
username: loginPageView.usernameTextField.rx.text.orEmpty.asObservable(),
password: loginPageView.passwordTestField.rx.text.orEmpty.asObservable())
//bind
viewModel.usernameVaild.bind(to: loginPageView.usernameValidUILabel.rx.isHidden)
.disposed(by: disposeBag)
viewModel.passwordVaild.bind(to: loginPageView.passwordValidUILabel.rx.isHidden)
.disposed(by: disposeBag)
viewModel.everythingVaild.bind(to: loginPageView.loginButton.rx.isEnabled)
.disposed(by: disposeBag)
}
}
执行一下专案,可以正常运作。
这样MVVM已经算是完成了,我们来对他做测试吧。
配置好业务逻辑需要的基本设定
class LoginPageViewModelTests: XCTestCase {
var sut : LoginPageViewModel!
var usernameObservable:Observable<String>!
var passwordObservable:Observable<String>!
var disposeBag:DisposeBag!
override func setUp() {
super.setUp()
disposeBag = DisposeBag()
}
override func tearDown() {
super.tearDown()
sut = nil
usernameObservable = nil
passwordObservable = nil
disposeBag = nil
}
}
配置完成後可以开始写测试函式了
func testLoginPageViewModel_usernameIsValid_true(){
//given
//when
//then
}
测试流程的注解。
func testLoginPageViewModel_usernameIsValid_true(){
//given
usernameObservable = Observable.create({ (observer) -> Disposable in
observer.onNext("12345")
observer.onCompleted()
return Disposables.create()
})
passwordObservable = Observable.create({ (observer) -> Disposable in
observer.onCompleted()
return Disposables.create()
})
//when
sut = LoginPageViewModel(
username: usernameObservable,
password: passwordObservable)
//then
sut.usernameVaild.bind { (bool) in
XCTAssertEqual(bool, true)
}.disposed(by: disposeBag)
}
command + u
测试成功
MVC在单元测试时,必须要实例化view(MVC变着不纳入讨论),这使单元测试偏离了原生单元测试的设计。因为单元测试就应该测试业务逻辑,他不关心UI上面的变化。
MVVM 与 MVC 的差异就是把业务逻辑抽离出来,这让单元测试上有很大的帮助,我可以更专注在业务逻辑上的测试,而不用担心View的生命周期。
<<: [Android Studio] -- Day 5 主题变换Theme02
那今天就把昨天的东西讲完吧 然後我觉得标题好难定 POPcat 这麽赞的内容竟然没人看 QQ 那这边...
前言 各位早安,书接上回我们安装好python跟Visual Studio Code,完成了开发环境...
该文章同步发布於:我的部落格 改变数值的时候 昨天提到变动性的问题是什麽呢? 我们到现在的测试都是...
那麽在先前实作中,我们业已将 WordPress 网站建筑在 AWS 环境中(可以详【Day 05】...
制造一个停止条件 我会在这边设置新变数 mat = 0 但是我也必须要有一个可以写入的函式 asyn...