Python开发入门与实战11-单元测试
11. 单元测试
本章节我们来讲讲django工程中如何实现单元测试,单元测试如何编写以及在可持续项目中单元测试的重要性。
下面是单元测试的定义:
单元测试是开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确。
1. 它是一种验证行为
程序中的每一项功能都是测试来验证它的正确性。它为以后的开发提供支援。就算是开发后期,我们也可以轻松的增加功能或更改程序结构,而不用担心这个过程中会破坏重要的东西,它为代码的重构提供了保障。这样,我们就可以更自由的对程序进行改进。
2. 它是一种设计行为
编写单元测试将使我们从调用者观察、思考。特别是先写测试(test-first),迫使我们把程序设计成易于调用和可测试的,即迫使我们解除软件中的耦合。什么时候测试?单元测试越早越好,早到什么程度?极限编程(Extreme Programming,或简称XP)讲究TDD,即测试驱动开发,先编写测试代码,再进行开发。
不过在实际的编码过程中,我们不必过分强调先干什么后写什么,重要的是高效和个人感觉舒适。从笔者的经验来看,根据设计或需求先编写某个功能函数的框架,然后就着手编写测试函数,针对产品的功能编写测试用例,最后编写函数的实现代码,每完成一个功能点都运行单元测试,随时补充完善测试用例。这种测试同行代码编写模式,会对函数的构思有很大的帮助,如何去编写可单元测试的函数慢慢的就会变成书写和思考习惯。
所谓先编写产品功能的函数框架,是指先编写函数空的实现,考虑参数的有哪些参数和可验证的返回值,同时默认直接返回一个合适值(假定值),编译通过后即可编写测试代码,这时,函数名、参数表、返回类型都应该确定下来了,所编写的测试代码以后需修改的可能性比较小,当然由于个人编码成熟度的不同,实际开发过程中调整也在所难免,好在单元测试可以迅速跟踪调准导致的影响。
3. 它是一种编写文档的行为
单元测试是一种无价的文档,它是展示函数或类如何使用的最佳文档。这份文档是可编译、可运行的,并且它保持最新,永远与代码同步。
4. 它具有回归性。
自动化的单元测试避免了代码出现回归,编写完成之后,可以随时随地的快速运行测试。笔者的经验表明一个尽责的单元测试方法将会在软件开发的早期阶段就可以发现很多的Bug,并且修改它们的成本也很低。在软件开发的后期阶段,Bug的发现和修改将会变得更加困难,尤其修改BUG可能导致引入新的BUG,并要消耗大量的时间和开发费用。
笔者的经历的项目就遇到这样的问题,项目上线的前一天的某个BUG修改,导致当晚一直加班深夜解决新引入的BUG问题,所以后来单元测试的回归性在笔者的项目经验里最喜欢的特性。无论什么时候作出修改都要进行完整的回归测试,可以避免修改可能引入的BUG。在生命周期中尽早地对软件产品进行测试将使效率和质量得到最好的保证。在提供了经过测试的单元的情况下,系统集成过程将会极大地简化。开发人员可以将精力集中在单元之间的交互作用和全局的功能实现上,而不是陷入充满很多Bug的单元之中不能自拔。
如果考虑做一个可以持续改进和维护的项目,尤其有大量的业务规则和逻辑的系统,单元测试就显得非常重要,单元测试主要这对业务逻辑编写测试代码,确定编码是否满足测试要求。下面我们就进入Django的单元测试实践吧。
11.1. 运行单元测试
我们创建好Django app 每个app 都会创建一个单元测试tests.py的单元测试文件,代码如下:
"""
This file demonstrates writing tests using the unittest module. These will pass
when you run "manage.py test".
Replace this with more appropriate tests for your application.
""" from django.test import TestCase class SimpleTest(TestCase): def test_basic_addition(self): """ Tests that 1 + 1 always equals 2. """ self.assertEqual(1 + 1, 2)
我们现在可以在IDE环境中,TEST->All Tests运行这个测试例子看看单元测试运行的效果。
11.2. 开始我们的第一个单元测试
我们来看看就提交入库单这个业务来说,目前的views.py函数AddInStockBill是没办法进行单元测试的,应为期参数涉及到web请求内容参数request,如何编写可具备单元测试的功能代码也是早期编写单元测试,可以让我们逐步掌握的代码解耦的思维模式。
入库单业务的关键点,就是入库单提交后我们需要更新该入库单对应物料的库存数据,伪代码如下:
1. 根据当前入库单的物料,在库存表中查找当前物料的库存记录;
2. 如果有当前库存记录返回当前库存对象,如果没有就创建一个新的对象;
3. 更新入库单对应物料的当前库存数据;
我们来看看如何尝试测试先行的开发模式去考虑一个入库单model提交将导致库存的更新场景,用代码说话吧:
from django.test import TestCase from inventory.models import *
from inventory import views class InventoryTest(TestCase): def test_updating_inventory_in(self): #1.创建一个Item实例;
item = Item()
item.ItemId = 1
item.ItemCode = ''
item.ItemName = '普通螺母' #2.床建一个新入库单对象
inStockBill = InStockBill()
inStockBill.InStockBillCode=''
inStockBill.InStockDate = '2015-01-01'
inStockBill.Operator = '张三'
inStockBill.Amount = 10
inStockBill.Item = item #3.创建当前该物料的库存对象
inventory = Inventory()
inventory.InventoryId = 1
inventory.Item = item
inventory.Amount = 10 #当前库存数量 #如何构建更新库存的函数,让其可具备测试调用
views.UpdatingInventoryIn(inStockBill,inventory) #校验测试是否满足当前场景
self.assertEqual(inventory.Amount ,20)
当前我们当前运行单元测试肯定会出错,因为我们还没有编写views.UpdatingInventoryIn函数:
def UpdatingInventoryIn(inStockBill,inventory): inventory.Amount = inventory.Amount + inStockBill.Amount
11.3. 执行单元测试
在IDE环境中执行改成我们写好的单元测试,我们看到结果如下图,测试通过。
这里我们的单元测试主要针对核心业务来构建,不考虑相关对象的获取方式,就是说测试用例是我们根据测试场景来构建的,不考虑对象是否在数据库中,也就是与持久层没有关系。早年笔者在这里也是大费周折,测试数据与持久层数据紧密耦合,结果更换数据库或者认为删除数据后,单元测试的回归测试就无法执行,单元测试的优势大打折扣。单元测试的回归性在后续代码重构,业务变更中有着巨大的优势,不能回归的单元测试价值就少了很多,所以我们在考虑单元测试时,一定要尽量与持久层数据解耦,测试用例数据在测试代码中构建。
11.4. 代码的持续改进
前面的代码中,我们的测试用例场景是假定该物料是已经有库存数据的,那如果该物料以前没有库存数据,我们的代码怎么来写呢,我们还是从测试用例开始吧,增加测试用例代码。
…
#校验测试是否满足当前场景
self.assertEqual(inventory.Amount ,20)
inventory = Inventory() #当前没有库存数据,我们创建对象属性都没有赋值
views.UpdatingInventoryIn(inStockBill,inventory)
self.assertEqual(inventory.Amount ,10)
self.assertEqual(inventory.Item.ItemId ,inStockBill.Item.ItemId)
执行测试错误,因为views.UpdatingInventoryIn(inStockBill,inventory)没有对inventory为空的情况处理,我改进代码来满足这样的需求。于是函数代码就变成了下面这样:
def UpdatingInventoryIn(inStockBill,inventory):
if (inventory.InventoryId == None):
inventory.Item = inStockBill.Item
inventory.Amount = 0
inventory.Amount = inventory.Amount + inStockBill.Amount
执行测试通过,刚才的测试用例是写在一个测试函数里还是分开写,主要是看我们的测试用例复杂度了,复杂度高的就分开来写,简单就写在一个测试函数里。函数粒度的选择由程序员来考虑了,核心就是关注可读性,函数太长我们就把函数拆小,提高可读性。(这里笔者强烈推荐《代码重构》这本很多年以前的经典书)。
最后们views里的增加入库单函数重构成如下这样代码:
@transaction.commit_on_success def AddInStockBill(request): if request.method == 'POST':
form = InStockBillForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
inStockBill = InStockBill()
inStockBill.InStockBillCode = cd['InStockBillCode']
inStockBill.InStockDate = cd['InStockDate']
inStockBill.Amount = cd['Amount']
inStockBill.Operator = cd['Operator']
inStockBill.Item = cd['Item'] inventorys = inStockBill.Item.inventory_set.all() if (inventorys.count()==0):
currentInventory = Inventory()
else:
currentInventory = inventorys[0] #注意的函数调用,你会发现更新库存与如何获取库存对象完全解耦
UpdatingInventoryIn(inStockBill,currentInventory)
currentInventory.save() #更新库存
inStockBill.save() #保存入库单数据 return HttpResponseRedirect('/success/') else: form = InStockBillForm()
return render_to_response('InStockAdd.html',{'form': form}
,context_instance = RequestContext(request))
11.5. 小结
如何编写单元测试代码,测试先行的模式会让编码人员去思考如何把业务逻辑抽象出来变成一个可以用单元测试来跟踪的函数单元很有帮助,如果我们在编写一个与数据库打交道的应用系统,把业务逻辑与如何获取数据解耦合对系统的可扩展性和可维护性相当的重要,尤其当我们打算构建一个可以持续改进的系统时尤为如此。
Python开发入门与实战11-单元测试的更多相关文章
- Python开发入门与实战1-开发环境
1.搭建Python Django开发环境 1.1.Python运行环境安装 Python官网:http://www.python.org/ Python最新源码,二进制文档,新闻资讯等可以在Pyth ...
- Python开发入门与实战8-基于Java的集成开发环境
8. 基于Java的Python的集成开发环境 目前为止我们所有的代码和例子都是通过Notepad文本编辑器来实现的,实际项目开发中这种编码模式效率较低(大虾除外),使用IDE集成开发环境常常大幅度的 ...
- Python开发入门与实战14-基于Extjs的界面
14. 基于Extjs的界面 上一章我们实现了一个原生的html例子,本章我们将采用Extjs实现界面的展现,来说明MVC模式下我们是怎么考虑界面与业务层的关系的. 14.1. 引用Extjs目录 首 ...
- Python开发入门与实战13-基于模板的界面
13. 基于模板的界面 本章我们将继续基于库存的简单例子来阐述如何在python django中体现MVC的架构,根据djangobook说明: M:数据存取部分,由django数据库层处理,本章要讲 ...
- Python开发入门与实战12-业务逻辑层
12. Biz业务层 前面的章节我们把大量的业务函数都放在了views.py里,按照目前这一的写法,当我们编写的系统复杂较高时,我们的views.py将会越来越复杂,大量的业务函数包含其中使其成为一个 ...
- Python开发入门与实战10-事务
1. 事务 本章我们将通过一个例子来简要的说明“事务”,这个开发实战里经常遇到的名词.事务是如何体现在一个具体的业务和系统的实现里. 事务是通过将一组相关操作组合为一个,要么全部成功要么全部失败的单元 ...
- Python开发入门与实战2-第一个Django项目
2.第一个Django项目 上一章节我们完成了python,django和数据库等运行环境的安装,现在我们来创建第一个django project吧,迈出使用django开发应用的第一步. 2.1.创 ...
- Python开发入门与实战20-微信开发配置
随着移动互联网时代的来临,微信已经成为移动互联网移动端的主要入口,现在很多的大型企业都有自己的微信服务号,如:银行业有自己的微银行,基金公司的公众服务号.通过微信入口可以方便快速的实现企业提供的服务. ...
- Python开发入门与实战19-Windows Azure web 应用部署
19. 微软云web应用部署 上一章节我们介绍了如何实现在微软云通过虚拟机部署我们的在python django应用,本章我们来介绍如何Windows Azure上部署通过部署网站的方式来部署我们的应 ...
随机推荐
- master page
<?xml version="1.0"?><configuration> <system.web> <pages clientID ...
- JSTL函数
JSTL包含了一系列标准函数. 引入:<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functi ...
- SQL标签
SQL标签库提供了与关系型数据库进行交互的标签. 引入语法:<%@ taglib prefix="sql" uri="http://java.sun.com/jsp ...
- Dynamics AX 2012 R2 耗尽用户
有时,你想执行一些操作,但是这些操作必须让所有用户都登出后才能执行,例如加强安全设置,创建虚拟公司等.这时,你可以使用系统的耗尽用户(Darin Users)功能.下面跟随Reinhard一起,设置耗 ...
- 本地yum库制作及本地安装Docker
生产环境中,我们总是会遇到服务器无法连接外网的情况,这样,如果想安装某个应用,而这个应用依赖的其他类库又特别多,就很痛苦了.这个时候,就需要自己制作个本地的yum库,进行本地安装.本文将以Docker ...
- CI框架区分前后台
基于官网 CodeIgniter-3.1.2版本修改 1.复制根目录下的 index.php 文件,重名为 admin.php .index.php用作前台入口:admin.php用作后台入口. 打 ...
- ADT中通过Android SDK Manager去安装x86的image时无法下载
参考:http://www.crifan.com/adt_android_sdk_manager_can_not_download_package_x86_image/
- IOS Xcode7 新建PCH文件
第一步:新建文件找到iOS中的Othere点击PCH File 点击Next 第二步:修改文件名为当前工程名(一般与工程名同名),勾选Targets,点击create创建 第三步:如图选中红框中的路径 ...
- css width="100" style ="width:100px" 区别
1. width="100"是正确的,而 width="100px"是错误的, style = "width:100px"是正确的 2. s ...
- 合并基因表达水平(merge gene expression levels, FPKM)
使用tophat和cufflinks计算RNA-seq数据的表达水平时,当一个基因在一个样本中有多个表达水平时需要合并它们的表达水平. This code is a solution to colla ...