本文节选自《OpenStack最佳实践测试与CI/CD》一书 由于OpenStack代码全部由Python编写实现,为了掌握如何使用Python编写测试程序,以及调试OpenStack代码,我们有必要掌握一些Python的相关技能。 新时期的IT发展对测试人员的相关能力提出了更高的要求,不仅要会手工测试,还要会自动化测试等。总之,掌握Python或其他编程语言的相关技能,有助于测试工作。 Python异常处理 Python提供了两个非常重要的功能来处理程序在运行中出现的异常和错误,即异常处理和断言。我们可以使用这两个功能来调试Python程序。本节讨论异常处理,断言会在下节阐述。 1.什么是异常 异常就是一个事件,该事件会在程序执行过程中发生,影响了程序的正常执行。 一般情况下,在Python没有正确处理程序时就会发生一个异常。 异常也是一个Python对象,表示一个错误。 当Python程序发生异常时,我们需要捕获处理异常,否则程序会终止执行。 在Python中,我们会经常遇到两种错误:语法错误(Syntax Error)和异常(Exception)。前者提示如“SyntaxError: invalid syntax”等,后者提示如“TypeError:****”等。 针对代码异常,Python的处理方式有try/except语句、try-finally语句、raise语句等多种方式。 (1)try/except语句 捕获异常可以使用try/except语句,该语句用来检测try语句体中的错误,而except语句用于捕获异常信息并处理。简单的try...except...else语法格式如下: 下面以OpenStack Nova计算项目中一个模块的实际代码段为例。代码路径:nova/cmd/api.py。 (2)try-finally语句 无论在try语句体中是否发生异常,try-finally语句都将执行最后finally语句体的代码。常见的语法格式如下: 该语句的示例代码如下: (3)raise语句 我们还可以使用raise语句自定义触发抛出异常。raise语法格式如下: 下面以OpenStack Nova项目中一个模块文件的实际代码段为例。代码路径:nova/api/auth.py。 2.用户自定义异常 通过创建一个新的异常类,程序可以命名自己的异常。异常应该通过直接或间接的方式继承自Exception类。需要注意的是,与标准异常相似,大多数异常的命名都以“Error”结尾。 在try语句体中,用户自定义异常后执行except语句,变量ex是用于创建MyError 类的实例。 其他异常处理方式,还有不常用的诸如使用traceback(跟踪)模块查看异常、sys模块回溯最后的异常等。 Python断言和断点 断言是指有条件地在程序代码中触发异常,如果表达式为False,则触发异常为AssertionError,如果没有被try捕获到就会终止程序。通常,断言用于测试相等性,比如在单元测试中测试某个函数执行的结果和预期值是否相等。 断点是指通过pdb、ipdb等方式对Python代码进行调试,当需要让被调试程序暂停时就要用到断点。通过暂停并敲入相应命令,我们能观察到变量、参数以及数据的运行,然后分析它们。关于该部分具体内容,见4.1.4节。 任何一种程序代码都会有异常,Python自然也不例外,如何有效处理代码异常是至关重要的。Python提供了众多的异常处理方法,如4.1.1节和本节所述。
其中,assert是断言的关键字。执行该语句时,先判断表达式expression,如果为真,则什么都不做;如果不为真,则抛出异常。reason则是异常类的实例。 我们可以看到,如果assert后面的表达式为真,则什么都不做;如果不为真,就会抛出AssertionError异常,而且我们传进去的字符串会作为异常类的实例信息存在。当然,assert异常也可以被try-except块捕获并处理。 Python单元测试 单元测试是对程序中最小的可测试单元进行测试,一般是函数、类或具有独立功能的语句体。单元测试一般在测试驱动开发(TDD)的背景下存在。Python主流的单元测试框架有doctest、nose、pytest和unittest四种。 在Python单元测试中有一种更高度的测试自动化接口,即discover函数,它能找出指定目录及其子目录下文件名符合某种样式的测试用例或测试模块,并放入到测试集中自动执行。下面是关于discover函数的描述。 通常,在测试软件时都会有层次、分模块、按顺序或者整体测试,discover接口为我们提供了这样的功能,指定不同的目录,就相当于指定不同的模块进行测试,指定根目录则代表要对产品进行整体测试。 1.doctest doctest模块可以在文档字符串(docstring)内嵌入注释以显示各种语句的期望结果,尤其是函数和方法的结果。这样做让文档字符串看起来就像是一个交互式的Shell会话。 2.nose nose是一个比unittest更加先进的测试框架。其优势在于:
缺点:不是Python标准库自带的,需要自行安装。 3.pytest pytest有时也被称为py.test,是因为它使用的执行命令是py.test。pytest与nose的基本用法很相似,pytest单元测试框架中并没有提供特殊的断言方法,而是直接使用Python的assert进行断言。它们的区别在于:
4.unittest unittest是Python自带的一个单元测试框架,原名为PyUnit,是由Java的JUnit衍生而来的。相比于其他单元测试库,unittest的使用更加广泛。下面以unittest的使用为例进行介绍。 (1)unittest基本使用步骤 import unittest,导入模块库。 (2)unittest整体结构 unittest模块库的组成部分包括: 通常,一个测试用例对应一个函数,且都以test_开头,如test_**。unittest整体结构如图4.1所示。 图4.1 (3)unittest常用的断言方法 对于测试而言,不管是功能测试、集成测试还是单元测试,一般都会预设一个正确的预期结果,而在测试执行的过程中会得到一个实际结果。测试成功与否就是拿实际结果与预期结果进行比较。这个比较的过程就是断言(assert)。 unittest库提供了很多实用的方法来检测程序运行的实际结果和预期结果,例如assertEqual()、assertIn()、assertTrue()、assertIs()等方法。如果给定的断言通过了,那么测试会执行下一行代码;反之,若断言没有通过,测试会暂停并且输出断言信息。这里包括三种方法,每一种都覆盖了典型的类型,比如检测相等值、逻辑比较、异常。 检测元素是否相等: 检测表达式是否为Ture或False: 检测异常: 逻辑运算: 正则表达式,检测是否匹配给定的文本: 检测字符串之间是否相等: 检测列表之间是否相等: (4)使用Test Fixtures Test Fixtures是unittest里面unitest.TestCase中最常用的方法。如果要对一个模块中的每一个测试函数都做同样的初始化操作和结尾清除等操作,那么创建n个测试用例就得写n遍一样的代码。为了减少重复的代码,可以使用下面两个函数。 setUp():用于执行测试前的初始化步骤。在每次执行测试用例之前调用,无参数,无返回值。该方法抛出的异常都视为error,而不是测试不通过。 tearDown():用于测试完成之后执行清除操作。在每次执行测试用例之后调用,无参数,无返回值。该方法抛出的异常都视为error,而不是测试不通过。只有setUp()调用成功了,该方法才会被调用。通过setUp和tearDown语句体组装成的模块,就是一个固定的测试装置。 而介于二者之间的test_**x,即测试用例,根据实际的功能代码逻辑来编写对应的测试项,在代码中以Python类方法的形式出现。assert**X即断言检查点,用于判断测试结果和预期结果是否相符。
(5)使用Test Suite组织测试代码 unittest.TestSuite(tests=()) 可以简单地把Test Suite理解成一个测试套件,在里面运行所有包含的测试用例。如下一些方法可以将测试用例添加到Test Suite中。 根据不同的功能,可能需要在不同的模块中选择一个或者几个测试用例进行组装。 返回该测试套件的函数: 更简洁的写法如下: 在测试套件中也可以包含测试套件: (6)使用TestLoader unittest.TestLoader类具有以下方法: 根据最佳实践,建议将测试用例程序、测试数据和公共代码等分别存放在不同的模块中,以方便重构使用;如果被测试对象发生了改变,也方便维护。 (7)跳过测试和预期的失败 unittest支持跳过单个测试方法甚至整个类的测试。使用skip()装饰器来设置跳过的特定条件,如指定在某个操作系统或软件版本等情况下不执行该测试。 代码执行时如果满足跳过条件,控制台会将后面的说明打印出来,并跳过该测试用例。跳过类的测试用法与之相处。除此之外,我们还可以自定义skip()装饰器。定义预期的失败使用unittest.expectedFailure(),运行时如果测试不通过,测试也不算作失败。 为编写单元测试,首先需要使测试用例类成为unittest模块中TestCase类的子类。测试方法必须以test开头。一个使用unittest模块编写的Python单元测试用例如下: 此外,提到Python单元测试,不能不提及与其紧密相关的“Mock”。Mock是Python中用于支持单元测试的一个库,它的主要功能是使用mock对象替换指定的真实对象,以达到模拟数据和操作的目的。 |
|
声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系
[邮箱地址] 删除
|