<Test-Driven Development with Python;学习笔记 第一部分 测试驱动开发基础

Posted PeterHo

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了<Test-Driven Development with Python;学习笔记 第一部分 测试驱动开发基础相关的知识,希望对你有一定的参考价值。

原创时间: 2016-01-06
更新时间: 2016-01-06

TDD简介

Test-Driven Development,驱动测试开发,是一种软件开发的开发方式.
它要求在编写某个功能代码之前先编写测试代码,然后只编写使测试通过的功能代码,通过测试来推动整个开发的进行.

我看的这本书叫,可以通过访问官网来购买或免费在线阅读
此书讲解TDD的方式是通过Django框架来开发一个Web应用.

我在学习的过程中感觉收获良多,因此将学习的过程和心得体会记录下来,希望对想要学习了解TDD的朋友也能提供一些帮助.

因为TDD的规则跟传统的开发方式不同,所以如果要使用TDD,就得转变以前的传统观念.这一点其实挺难的

测试分类

功能测试 Functional Test

以用户的角度来在外部测试应用,可以写一个小故事,来模拟用户的整个操作流程,根据这个流程来写出测试代码,从这个角度上说功能测试可以看作是你开发的应用(或者模块)的一份使用说明书

功能测试可以只在一个模块内,也可以包含多个模块,不过按道理讲用户是不了解也不需要了解他们需求的功能是在一个模块内还是包含多个模块的,所以我认为功能测试应该是一个全局的测试方式

功能测试也叫验收测试,端对端测试,黑盒测试

功能测试使用的主要的工具是Selenium,一个可以控制浏览器行为的工具集

单元测试 Unit Test

程序员的角度从内部来测试应用,测试的内容是某个模块内部的具体实现

单元测试只负责测试某个模块内的功能

TDD开发流程

先借用书中的一张图来有一个直观的印象

  1. 遍写一个功能测试
    从用户角度用故事的方式来描述新功能,将故事翻译成测试代码

  2. 运行功能测试
    如果功能测试出错,就考虑如何编写应用代码来通过测试,
    如果功能测试出现的异常是预料中的,其实是好事,至少我知道下一步的工作是什么
    如果功能测试出现了非预期的异常,需要仔细阅读出错代码,很可能是因为之前的修改导致了更多的错误.可以通过多种方式来调试代码:

    • 添加print语句打印运行过程
    • 改进错误输出来获得更多的当前状态的信息
    • 手动访问网站
    • 添加sleep函数来暂停运行过程
  3. 编写一个(多个)单元测试
    针对上一步想到的编码方式,编写对应的单元测试

  4. 运行单元测试

  5. 编写应用代码
    针对单元测试提示的错误来编写尽可能少的应用代码,仅仅是为了通过这次测试
    循环第3,4,5步,直到通过全部单元测试并且觉得能通过第1步编写的功能测试

  6. 运行第1步所编写的功能测试
    如果能通过,则可以向前一小步了
    如果不能通过,可能需要编写更多的单元测试和应用代码,继续循环第3,4,5步

  7. 重构代码
    不修改代码的功能而又对代码进行修改的过程就叫重构
    当通过单元测试或功能测试时考虑是否需要重构代码,注意在重构的过程中也要遵循TDD的规则,每重构一小步就要运行一次测试,一直到重构完成.当测试全部通过时,又回到第1步(或第3步)
    重构代码也包括修改单元测试的代码,但是注意修改测试代码和修改应用代码不能同时进行

以上的流程看起来很繁琐,也很无趣,还很浪费时间,但是我觉得这样做有他的道理和好处

  1. 保持编码的连续性,当功能测试写好以后,整体目标就定下来了,以后的工作就是根据测试提示的错误来一步一步的接近目标,而不会受到其他的影响,比如在实现功能的过程中又有其他的想法,转而开发其他的模块,也不会过早地进行重构

  2. 在整个开发过程中只需要考虑怎么通过测试就好了,不需要考虑太多的其他问题,这样的开发过程也会相对轻松

  3. 整个开发过程都受到单元测试的保护,保证不会出现错误,也不会破坏以前已经实现了的功能和模块

  4. 在开发简单功能时会感觉很无趣,但是当代码结构已经很复杂时,可以避免被复杂的代码搅晕

  5. Kent Beck有一个很好的比喻,开发的过程就像是从井里打水,当井不深水桶也不够满时,这个过程很容易,但如果要从一个很深的井里面打一桶很满的水,这个过程就很困难了.而TDD就像在井口安装的一个棘轮,它不一定能让你打水更轻松,但能让你在打水的过程中能稍事休息,还能保证你的水桶不会突然滑下去.就像打RPG游戏时的SL大法一样(这个比喻是我想出来的^_^)

  6. TDD是一种纪律,如果不一开始就对你要求严格,以后你对自己的要求会越来越松的

TDD的规则

在做任何事情之前:写测试

这是TDD最基本的规律,在做任何事情之前(添加功能,修改功能,重构代码),都要先把测试代码写好.一开始会感觉这种方式反人类又浪费时间,其实在熟练运用以后会体会到这样开发的好处

每次只走一小步

当功能测试通过后需要添加新的功能时,不要想太远,只向前走一小步,添加一点点新的功能就好.

当单元测试失败时,只改动最少的代码量(一次一行)以通过当前测试就可以了

我每次写代码都会忍不住把想到的都写出来,想改的都改完,这样的结果就是经常把代码搞得一团糟,连以前已经实现的功能都不能正常运行了.反而会花更多的时间来调试混乱的代码.

关于每次只改动最少的代码量这个问题我是这样来理解的,每次改的最小代码量指的是你最容易控制的代码量,不一定非得只能是一行

比如说views里面缺少一个方法home_page来处理一个url,对于初学者,修改迭代的步骤是这样的:

# 第一步,定义一个home_page空对象
home_page = None
# 第二步,将home_page空对象改为一个空方法
def home_page():
    pass
# 第三步,对home_page方法添加必须的参数
def home_page(request):
    pass
# 第四步,修改home_page方法的返回值,使之返回HttpResponse对象
def home_page(request):
    HttpResponse()
# 第五步,返回正确的响应
def home_page(request):
    HttpResponse('something')

但是对于一个已经熟练掌握Django的程序员来说,我认为直接写成如下形式是没问题的

def home_page(request):
    pass

使用版本控制

基本的规则就是当需要改功能加功能或者重构之前,先commit一次
因为我依赖IDE的history功能,所以commit得没那么频繁

一定要压抑住自己的创作热情

在做任何改动之前先写测试,如果你因为自以为代码逻辑简单就忍不住越写越多,当你意识到代码足够复杂时,你已经是那只被煮熟的青蛙了.

每个测试方法只测试一件事

每个单元测试的方法要尽可能的小,每个测试方法只测试一件事情

这样的方式会让你在测试失败时轻松定位失败的原因

并且如果当测试方法中前面的测试出错时,你也不知道后面的测试是否通过

不要测试常量

单元测试的目的是测试逻辑,运行流控制,配置等,而不是测试常量.如果在应用代码里写了

wibble = 3

这时如果写这样的测试代码就没有多大意义了

assert wibble == 3

如果需要测试html字符串,将HTML字符串写在模板(templates)里面

重构代码就只重构代码

在重构完成之前一定不能增加或修改功能,重构就只是重构,这也是我之前在开发时经常会犯的错误,经常在重构时又搞到其他地方去了.同样的道理,在重构没有完成之前也不要修改测试代码

重构代码的时机

  • 三则重构原则(Three Strikes and Refactor)

当第一次复制已有的代码时(这样相同代码就出现了两次),忍了,不要急着来重构它

当再一次复制相同的代码时,这时相同的代码已经重复三次了,重构吧,注意重构之前先commit一次代码

遵循这个原则可以避免过早的重构代码

尽量不要中断

如果在写测试代码或编码时突然有什么新的想法,尽量不要中断当前的工作

拿一张便条或者在TODO文档中记录下来,等这轮unit-test/code周期完成后,再来处理

或者等功能测试通过后,添加为新的功能

YAGNI

YAGNI(You aint gonna need it!)意为你不需要它.每个开发人员都乐于创造,有时很难压抑马上实现一些头脑中突然冒出来的想法的冲动,但这些想法很可能是不需要的.

因此在刚开始设计应用时不要设计得太复杂,功能也不要太多,只要设计最基本的功能就好了

更复杂的设计和功能在开发中根据现实的需求逐渐加入.

这一点我觉得很有道理,以前在早期设计时总想着如何把所有可能的功能和需求都考虑到,浪费了很多的时间和精力,但真正在开发中会发现需求在不断地变化,以前设计的很复杂的模型可能根本就没用.

TDD开发中的一些技巧和需要注意的问题

  • 在编写针对某个views方法的单元测试时,首先需要测试两点:

    1. 系统是否能将请求送达到对应的views方法

    2. views方法是否能返回我们需要的响应

  • 每次处理完POST请求以后都要重定向

  • 在功能测试代码中用两个井号”##”开头的注释来作为注释的注释(元注释)以区分普通的注释
    在功能测试中普通的注释主要描述用户在干什么
    而元注释则解释测试代码是如何工作和这么工作的原因

  • 如果在功能测试时出现了什么奇怪的问题,先升级Selenium

pip3 install --upgrade selenium

参考代码

此文包含书中的第1-6章,每一章的代码对应代码库中的一个分支,其中

以上是关于<Test-Driven Development with Python;学习笔记 第一部分 测试驱动开发基础的主要内容,如果未能解决你的问题,请参考以下文章

测试驱动开发(Test-Driven Development)

软件工程 - Test-Driven Development (TDD),测试驱动开发

react初学

input 只能输入数字,限定输入

个人项目-地铁出行路线

.NET单元测试