Python开发入门与实战11-单元测试

1. 单元测试

本章节我们来讲讲django工程中如何实现单元测试,单元测试如何编写以及在可持续项目中单元测试的重要性。

下面是单元测试的定义:

单元测试是开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确。

1. 它是一种验证行为

程序中的每一项功能都是测试来验证它的正确性。它为以后的开发提供支援。就算是开发后期,我们也可以轻松的增加功能或更改程序结构,而不用担心这个过程中会破坏重要的东西,它为代码的重构提供了保障。这样,我们就可以更自由的对程序进行改进。

2. 它是一种设计行为

编写单元测试将使我们从调用者观察、思考。特别是先写测试(test-first),迫使我们把程序设计成易于调用和可测试的,即迫使我们解除软件中的耦合。什么时候测试?单元测试越早越好,早到什么程度?极限编程(Extreme Programming,或简称XP)讲究TDD,即测试驱动开发,先编写测试代码,再进行开发。

不过在实际的编码过程中,我们不必过分强调先干什么后写什么,重要的是高效和个人感觉舒适。从笔者的经验来看,根据设计或需求先编写某个功能函数的框架,然后就着手编写测试函数,针对产品的功能编写测试用例,最后编写函数的实现代码,每完成一个功能点都运行单元测试,随时补充完善测试用例。这种测试同行代码编写模式,会对函数的构思有很大的帮助,如何去编写可单元测试的函数慢慢的就会变成书写和思考习惯。

所谓先编写产品功能的函数框架,是指先编写函数空的实现,考虑参数的有哪些参数和可验证的返回值,同时默认直接返回一个合适值(假定值),编译通过后即可编写测试代码,这时,函数名、参数表、返回类型都应该确定下来了,所编写的测试代码以后需修改的可能性比较小,当然由于个人编码成熟度的不同,实际开发过程中调整也在所难免,好在单元测试可以迅速跟踪调准导致的影响。

3. 它是一种编写文档的行为

单元测试是一种无价的文档,它是展示函数或类如何使用的最佳文档。这份文档是可编译、可运行的,并且它保持最新,永远与代码同步。

4. 它具有回归性。

自动化的单元测试避免了代码出现回归,编写完成之后,可以随时随地的快速运行测试。笔者的经验表明一个尽责的单元测试方法将会在软件开发的早期阶段就可以发现很多的Bug,并且修改它们的成本也很低。在软件开发的后期阶段,Bug的发现和修改将会变得更加困难,尤其修改BUG可能导致引入新的BUG,并要消耗大量的时间和开发费用。

笔者的经历的项目就遇到这样的问题,项目上线的前一天的某个BUG修改,导致当晚一直加班深夜解决新引入的BUG问题,所以后来单元测试的回归性在笔者的项目经验里最喜欢的特性。无论什么时候作出修改都要进行完整的回归测试,可以避免修改可能引入的BUG。在生命周期中尽早地对软件产品进行测试将使效率和质量得到最好的保证。在提供了经过测试的单元的情况下,系统集成过程将会极大地简化。开发人员可以将精力集中在单元之间的交互作用和全局的功能实现上,而不是陷入充满很多Bug的单元之中不能自拔。

如果考虑做一个可以持续改进和维护的项目,尤其有大量的业务规则和逻辑的系统,单元测试就显得非常重要,单元测试主要这对业务逻辑编写测试代码,确定编码是否满足测试要求。下面我们就进入Django的单元测试实践吧。

1.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运行这个测试例子看看单元测试运行的效果。

1.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 = ‘1001‘
    item.ItemName = ‘普通螺母‘

    #2.床建一个新入库单对象
    inStockBill = InStockBill()
    inStockBill.InStockBillCode=‘201501010001‘
    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

1.3. 执行单元测试

在IDE环境中执行改成我们写好的单元测试,我们看到结果如下图,测试通过。

这里我们的单元测试主要针对核心业务来构建,不考虑相关对象的获取方式,就是说测试用例是我们根据测试场景来构建的,不考虑对象是否在数据库中,也就是与持久层没有关系。早年笔者在这里也是大费周折,测试数据与持久层数据紧密耦合,结果更换数据库或者认为删除数据后,单元测试的回归测试就无法执行,单元测试的优势大打折扣。单元测试的回归性在后续代码重构,业务变更中有着巨大的优势,不能回归的单元测试价值就少了很多,所以我们在考虑单元测试时,一定要尽量与持久层数据解耦,测试用例数据在测试代码中构建。

1.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))

1.5. 小结

如何编写单元测试代码,测试先行的模式会让编码人员去思考如何把业务逻辑抽象出来变成一个可以用单元测试来跟踪的函数单元很有帮助,如果我们在编写一个与数据库打交道的应用系统,把业务逻辑与如何获取数据解耦合对系统的可扩展性和可维护性相当的重要,尤其当我们打算构建一个可以持续改进的系统时尤为如此。

时间: 2024-08-06 16:01:21

Python开发入门与实战11-单元测试的相关文章

Python开发入门与实战1-开发环境

1.搭建Python Django开发环境 1.1.Python运行环境安装 Python官网:http://www.python.org/ Python最新源码,二进制文档,新闻资讯等可以在Python的官网查看到. Python3.0已经发布,本文我们使用Django作为对象映射层,Django暂时还不支持3.0版本,本文我们以Python 2.7 Windows 8 64位版本为例.下载安Windows X86-64 MSI Installer (2.7.7) [1]安装包,运行安装文件.

Python开发入门与实战8-基于Java的集成开发环境

8. 基于Java的Python的集成开发环境 目前为止我们所有的代码和例子都是通过Notepad文本编辑器来实现的,实际项目开发中这种编码模式效率较低(大虾除外),使用IDE集成开发环境常常大幅度的提高编码效率.本章我们将简要介绍两个主流的集成开发环境. 8.1. 下载安装Java运行环境 http://www.java.com/zh_CN/download/manual.jsp 根据操作系统版本下载安装Java运行时环境,如下图: 8.2.Eclipse IDE http://www.ecl

Python开发入门与实战10-事务

1. 事务 本章我们将通过一个例子来简要的说明“事务”,这个开发实战里经常遇到的名词.事务是如何体现在一个具体的业务和系统的实现里. 事务是通过将一组相关操作组合为一个,要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠.事务具有4个特性:原子性.一致性.隔离性.持久性.业务事务就是完成具体业务操作后,形成的业务结果:数据库事务是数据库产品根据事务的特性实现的相关功能,数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地

Python开发入门与实战14-基于Extjs的界面

14. 基于Extjs的界面 上一章我们实现了一个原生的html例子,本章我们将采用Extjs实现界面的展现,来说明MVC模式下我们是怎么考虑界面与业务层的关系的. 14.1. 引用Extjs目录 首先,我们在inventory app下增加一个static目录,拷贝Extjs发布目录到static下,本章节例子我们采用的是Extjs 4.1.1版本进行说明演示,Django项目能够访问static目录我们需要修改项目setting.py的STATIC_ROOT项的值,项目才能正确装载引用的静态

Python开发入门与实战12-业务逻辑层

12. Biz业务层 前面的章节我们把大量的业务函数都放在了views.py里,按照目前这一的写法,当我们编写的系统复杂较高时,我们的views.py将会越来越复杂,大量的业务函数包含其中使其成为一个包罗万象的文件.本章我们将阐述增加一个业务逻辑层来解决view层的复杂度,相当于在model层和view层中增加一个业务逻辑业务层Biz层,接下来我们根据这个思路来重构我们前面章节的代码. 12.1. 增加inventoryBiz类文件 在inventory app添加一个新的inventoryBi

Python开发入门与实战2-第一个Django项目

2.第一个Django项目 上一章节我们完成了python,django和数据库等运行环境的安装,现在我们来创建第一个django project吧,迈出使用django开发应用的第一步. 2.1.创建第一个Django项目 我们创建一个我们存放Django的工作目录,示例:C:\My Files\Python Projects 在命令提示符窗口进入到刚才创建的目录,运行运行命令: django-admin.py startproject mysite 这样会在你的当前目录下创建一个目录mysi

Python开发入门与实战19-Windows Azure部署2

19. 微软云部署2 上一章节我们介绍了如何实现在微软云通过虚拟机部署我们的在python django应用,本章我们来介绍如何Windows Azure上部署通过部署网站的方式来部署我们的应用,这种部署方式更方便,与vs 2013的集成度更高. 19.1. 创建Web 应用 我们登陆Windows Azure中国账户后进入到管理门户,选择“web 应用”,点击新建按钮,如下图: 创建完成后,我们选择myazure进度到应用的管理界面,如下图: 点击“浏览”按钮,浏览该网站,如果网站创建成功浏览

Python开发入门与实战9-基于vs的集成开发环境

9. 基于visual studio的Python的集成开发环境 上一章我们描述了如何安装使用Java的集成开发环境Eclipse IDE,本章我们来说明另一种集成开发环境,也是笔者多年的开发习惯使用的环境,也由于这个原因,接下来的章节描述都将以本章说明的集成开发环境为例. 微软的Visual Studio系列:一种在国内使用非常广泛的集成开发环境.这里我们选择的版本是community 2013版本.自从Visual Studio 也有支持Python的开发插件后,笔者很快就又重新转移到Vis

Python开发入门与实战18-Windows Azure部署

18. 微软云部署 上一章节我们介绍了如何在新浪云部署我们的在python django应用,本章我们来介绍如何Windows Azure上部署我们的应用. 18.1. 注册Windows Azure中国试用账号 网址:http://www.windowsazure.cn/ 获得激活邮件后,登记激活试用账号账户成功后(需要支付1元费用),即试用Windows Azure云服务. 18.2. 登陆账号,进入到门户 来到你的微软云管理界面,如下图: 本次部署我们采用云虚拟机的方式来部署python