iOS APP iOS Test-Driven Development by Tutorials free section 学习笔记-the TDD Cycle

iOS APP iOS Test-Driven Development by Tutorials free section 学习笔记-the TDD Cycle

tags: TDD day

TDD Cycle

上一篇笔记,您了解了测试驱动的开发可以归结为一个简单的过程,称为TDD Cycle。它有四个步骤,通常被“color coded”,如下所示:
我们称它为“Red-Green-Refactor Cycle”

图片来源
颜色是什麽意思?

  • 失败时,以 Red为颜色标记
  • 通过时,以 green 为颜色标记
  • 通过後进入 Refactor阶段

不断的循环。

注意

我的笔记会直接跳过playground的章节,建议看完playground的章节再看我的笔记:
the TDD Cycle

Getting started

clone我的专案,尝试我建制专案的过程吧。
clone Alvin的专案

需求

我希望Login in 的 button ,在帐号与密码不符合规范时,是不能按的。

graph LR;
 button.state是disable-->|达成条件|button.state是normal
graph LR;
 button.state是normal-->|未达成条件|button.state是disable

Red : Write a failing test

为了让程序码有好的可读性,我们在测试的命名要注意

Test nomenclaturn

试着遵循一些TDD命名法则。我们来看看范例:

func testAppModel_whenStarted_isInInProgressState() {}

test funtion 的名称应该要描述test。test名称显示在test logs中。当发生错误时,必续邀可以立即了解问题所在。所以要避免test1,test2这种命名。
这里使用的命名方案最多包括四个部分:

  1. 所有测试必须以开头test。
  2. AppModel这表示一个AppModel正在测试的系统(sut)。
  3. whenStarted 是条件或状态变化,是条件的触发者。
  4. isInInProgressState当when发生後状态应该受到的变化。

Try it

我要测试的条件为:
当LoginPageViewController在帐号与密码都已经服合条件时,loginButton的state要是Normal的

func testLoginPageViewController_loginStateIsBothCorrect_loginButtonIsNormal(){}

接着在里面放入三个注解

    func testLoginPageViewController_loginStateIsBothCorrect_loginButtonIsNormal(){
        //given
        // when
        //then
    }

这是为了方便理解建制这个 Test的流程。

  • Given 在特定的条件下
  • When 当某个行为发生时
  • Then 预期要发生的结果

Given 帐号与密码都符合条件下

    func testLoginPageViewController_loginStateIsBothCorrect_loginButtonIsNormal(){
        //given
        let state:LoginState = .bothCorrect
        // when
        //then
    }

When 当LoginPageViewController为这个状态时

    func testLoginPageViewController_loginStateIsBothCorrect_loginButtonIsNormal(){
        //given
        let state:LoginState = .bothCorrect
        // when
        sut.loginState = state
        //then
    }

Then loginButton的state应该要是normal。

    func testLoginPageViewController_loginStateIsBothCorrect_loginButtonIsNormal(){
        //given
        let state:LoginState = .bothCorrect
        // when
        sut.loginState = state
        //then
        let loginState = sut.loginPageView.loginButton.state
        XCTAssertEqual(loginState, UIButton.State.normal)
    }

开始测试

按下 command + U 开始测试。

结果测试错误,别担心。这是一定会遇到的流程。我们来尝试通过测试。

Green : Make the test pass

1. 写一个Loginable的 protocol

protocol Loginable {
    func changeLoginButtonState(loginState:LoginState)
}

2. 让 LoginPageViewController conform Loginable 这个 protocol 然後delegate也设置完成。

import UIKit
protocol Loginable {
    func changeLoginButtonState(loginState:LoginState)
}
class  LoginPageViewController: UIViewController{
    let loginPageView = LoginPageView()
    public var loginState:LoginState = .bothError
    //MARK: - loadView()
    override func loadView() {
        self.view = loginPageView
        loginPageView.delegate = self
    }
    //MARK: - viewDidLoad()
    override func viewDidLoad() {
        super .viewDidLoad()
    }
}
    //MARK: Loginable
extension LoginPageViewController:Loginable{
    func changeLoginButtonState(loginState: LoginState) {
        if loginState == .bothCorrect {
            loginPageView.loginButton.isEnabled = true
        }
    }
}

但是仅仅这样是不够的,我还要能够监测UITextField有没有输入文字,加入UITextField输入的delegate。

import UIKit
protocol Loginable {
    func changeLoginButtonState(loginState:LoginState)
}
class  LoginPageViewController: UIViewController{
    let loginPageView = LoginPageView()
    public var loginState:LoginState = .bothError{
        didSet{
            changeLoginButtonState(loginState: self.loginState)
        }
    }
    
    //MARK: - loadView()
    override func loadView() {
        self.view = loginPageView
        loginPageView.loginableDelegate = self
        loginPageView.usernameTextField.delegate = self
        loginPageView.passwordTestField.delegate = self
    }
    //MARK: - viewDidLoad()
    override func viewDidLoad() {
        super .viewDidLoad()
    }
}
    //MARK: Loginable
extension LoginPageViewController:Loginable{
    func changeLoginButtonState(loginState: LoginState) {
        if loginState == .bothCorrect {
            loginPageView.loginButton.isEnabled = true
        }
    }
}
extension LoginPageViewController:UITextFieldDelegate{
    func textFieldDidEndEditing(_ textField: UITextField) {
        if textField.text!.count > 10{
            loginState = .bothCorrect
            changeLoginButtonState(loginState: loginState)
        }
    }
}

接着 command + U 再测试一次

很好,测试成功了。

3. 接下来,进入 Refactor 的流程

我重构了LoginPageViewController,将判断帐号与密码是否输入的逻辑切出去。

import UIKit
protocol Loginable {
    func changeLoginButtonState(loginState:LoginState)
}
class  LoginPageViewController: UIViewController{
    let loginPageView = LoginPageView()
    public var loginState:LoginState = .bothError{
        didSet{
            changeLoginButtonState(loginState: self.loginState)
        }
    }
    
    //MARK: - loadView()
    override func loadView() {
        self.view = loginPageView
        loginPageView.loginableDelegate = self
        loginPageView.usernameTextField.delegate = self
        loginPageView.passwordTestField.delegate = self
    }
    //MARK: - viewDidLoad()
    override func viewDidLoad() {
        super .viewDidLoad()
    }
}
    //MARK: Loginable
extension LoginPageViewController:Loginable{
    func changeLoginButtonState(loginState: LoginState) {
        if loginState == .bothCorrect {
            loginPageView.loginButton.isEnabled = true
        }
    }
}
    //MARK: UITextFieldDelegate
extension LoginPageViewController:UITextFieldDelegate{
    func textFieldDidEndEditing(_ textField: UITextField) {
        let tag = textField.tag
        if textField.text!.count > 10{
           loginState =  loginState.updateState(tag: tag)
        }
    }
}

  1. 再一次的 unit test

    如果验证是成功的,代表重构是成功的。

开始

我们完成了一次 TDD Cycle,别忘了 TDD 是迭代开发软件的方法,所以这只是第一步,这或许是很漫长的,但是每个扎实的步伐,将会带领你的程序码步向於完美。
下一个 TDD Cycle


<<:  NETGEAR WIFI EXTENDER SETUP

>>:  什麽是帕累托图?(20/80法则)

JS 23 - 非同步执行,也是要依序排队!

大家好! 今天我们要实作的静态函式有点像 Promise 的感觉。 我们进入今天的主题吧! 程序码 ...

[WMX3] 2.WMX3Console

WMX3Console 主要用於伺服马达与IO输出输入模拟,很多时候在还没有实际物件(伺服马达,I...

Day17:17 - 结帐服务(1) - 後端 - 结帐、订单新增API

Здравейте,我是Charlie! 在Day16当中,我们完成了前端的购物车新增跟删除,而今天...

资料分析商业应用与策略管理 #笔记四

昨天提到了 Python,那就不能不介绍资料科学中相当重要、可以说是 Python 懒人包的 Ana...

找LeetCode上简单的题目来撑过30天啦(DAY12)

题号:15 标题:3Sum 难度:Medium Given an integer array num...