unittest+DDT+HTMLReport组合框架实现IPMI协议自动化测试

1、IPMI协议与ipmitool

IPMI协议是一个开放标准的硬件管理接口,通过基板管理控制器(BMC)进行进行低级硬件智能管理,独立于其他硬件和操作系统。用户可以利用IPMI 监视服务器等设备的物理特征,如各部件的温度、电压、风扇工作状态、电源供应以及机箱入侵等。IPMI协议最新的版本是IPMI 2.0。下面是整个管理平台的架构。

ipmitool是一种在linux系统下以命令行的形式调用IPMI协议平台管理工具。它有两种使用方式

带内方式

在操作系统内部使用OS提供的接口与BMC通讯,操作系统需要安装OpenIPMI驱动;ipmitool本地监控使用命令:

其中表示使用OpenIPMI接口,表示具体的命令。

带外方式

被监控服务器只需要有BMC芯片,可以无需安装驱动与ipmitool。监控客户端只需要安装ipmitool,无需硬件和操作系统接口驱动的支持。

IPMI的带外监控是通过向与BMC相连的网络接口发送udp数据包实现的,udp数据包的定位是通过把ip地址写BMC芯片来实现,同时BMC中保存了序列化的用户名密码,因此通过LAN进行远端访问时需要提供用户名和密码。ipmitool远程监控使用命令:

其中表示使用LAN的方式,表示BMC管理网口地址,表示用户名,表示密码,表示具体的命令。

下面主要介绍的是带外模式下的IPMI协议的自动化测试设计。

2、自动化分析

ipmitool本身是个linux下的工具,通过使用Cygwin接口(这个工具很有意思,后续可以研究一下)实现了windows下的exe版本,在CLI(command-line interface)下输入相应的命令即可调用IPMI协议(windows在cmd下操作),服务器接受到命令后,会对命令进行响应,返回响应状态码并将响应输出结果直接打印到控制台上,部分命令也可能配置了某些参数或是执行了某种动作。

对IPMI协议正常与否该如何断言?最直接的想法一定是断言命令返回信息和预期结果的一致性,这是协议测试最常见的断言方式,但应用到这里会有几个问题:

预期结果变数大、复杂度高。部分命令在正常响应的情况下,返回信息是不变的,例如power on,返回的永远是Chassis Power Control: Up/On;但是大部分命令则是根据实际配置或者实际状态返回信息,例如fru,对不同服务器都会发生变化。而sensor的值甚至时刻都在变化,无法有效对比。即使在一次测试中,需要断言的数据不会变化,但是每次测试前都要根据实际情况去配置大量的预期结果,非常耗费时间与精力,还不如手工测试一遍效率高的情况下,就失去了自动化的意义。

原有测试用例中的测试验证流程是在手工测试的前提下形成的。即使这种断言方式也无法覆盖到整个验证过程。因为,协议返回的信息并不就一定代表了被测服务器真实响应行为,例如,协议返回了Chassis Power Control: Up/On,服务器就真的开机了?还是需要别的手段确认过,才算测试完整。一旦“别的手段”很难以自动化的方式实现,就会导致在手工测试的介入。本来一次性检查的内容,在引入自动化后出现了割裂——自动、手动、自动、手动……这种穿插式半自动的体验,会让人难以接受,是自动化设计应该极力避免的情况。

协议测试理应是更简单更纯粹的东西,这样才能减少出错概率,并且尽可能地提升执行效率。所以,如果一定要对IPMI协议自动化,就应该提倡分层测试,排除多余的期望,减少自动化的职责范围——协议测试部分仅针对测试服务器对协议命令的响应做单元测试,单纯通过断言命令响应状态码是否等于0来判定协议应答是否正常,不再考查命令执行返回信息的具体内容(当然还是会提供自动化完成后手工检查的日志信息)与服务器的实际反应。而这些测试点的确认,实际上可以放到更上层的应用——BMC界面的功能测试(基于IPMI协议的web界面)中,人为地去检查覆盖。

所以最后,IPMI协议的自动化在我这里被定位为“一种极其便捷地去验证服务器是否存在IPMI协议响应问题的方法”,这也是贯穿这个工具的设计过程的基本理念。

3、subprocess

subprocess是Python2.4之后版本标准库中默认模块,包含了下面这些常用函数:

call函数和check call函数的区别是,call函数异常时只返回错误信息,不会抛具体异常,而check call函数会抛出异常。

check output则与上面两个函数的不同在于返回的不是命令执行状态,而是命令执行结果,执行结果不再打印到执行窗口的信息中,状态不为0时也会抛出异常。

run函数是 Python 3.5中新增的函数,也是官方推荐使用的函数。执行指定的命令,等待命令执行完成后返回一个包含执行结果的CompletedProcess类的实例,不会直接打印结果。异常时返回只错误信息,异常被记录在CompletedProcess类中的stderr属性中,也不会被打印出来。

getoutput函数与getstatusoutput函数是从Python 3.3.4开始才支持Windows平台。getoutput函数多了一步从控制台接收命令的过程。而getstatusoutput函数与其他函数的主要差别是返回值的数量上面,即返回了命令执行状态,也返回命令执行结果输出。

从API的描述看,上面所有的函数都可以满足发送控制台命令的需求。由于需要使用命令执行状态来进行断言,同时执行结果最好也能获取以备他用,所以最满足条件的是run函数以及getstatusoutput函数,这里采用构造比较简单的getstatusoutput函数,并需要将python环境升级到3.5版本。

4、unittest+DDT+HTMLReport

4.1 unittest,从入门到放弃?

确认了命令调度与返回的方式后,需要组织起测试驱动的模块。unittest是python自带的单元测试模块,它包含了下面几大核心概念:

TestCase(测试用例): 所有测试用例的基类,它是测试执行最基本的单元。

TestSuite(测试套件):多个TestCase的集合就是TestSuite,TestSuite可以嵌套TestSuite。

TestLoder:是用来加载 TestCase到TestSuite中,其中有几个loadTestsFrom_()方法,就是从各个地方寻找TestCase,创建他们的实例,然后add到TestSuite中,再返回一个TestSuite实例。

TextTestRunner:是来执行测试用例的,其中的run(test)会执行TestSuite/TestCase中的run(result)方法。

TextTestResult:测试结果会保存到TextTestResult实例中,包括运行了多少用例,成功与失败多少等信息。

TestFixture:又叫测试脚手,测试代码的运行环境,指测试准备前和执行后要做的工作,包括setUp和tearDown方法。

整个单元测试组织流程如下:

写好TestCase:继承unittest.TestCase的一个Class,就是一个测试用例类,其中所有以test开头的方法,就是具体的测试项目,在load的时候会生成一个TestCase实例。如果一个class中有四个test开头的方法,最后load到suite中时则有四个测试用例。

由TestLoder加载TestCase到TestSuite。

然后由TextTestRunner来运行TestSuite,运行的结果保存在TextTestResult中,默认会将结果输出到控制台。

通过命令行或者unittest.main()执行时,main会调用TextTestRunner中的run来执行

本次自动化的具体测试设计思路是,将IPMI协议命令以外部文件的形式数据化,传入TestCase。在TestCase中,根据可配置参数IP、user以及password重新组合成实际命令,通过调度getstatusoutput函数返回命令执行状态,最后断言状态是否为0来决定是pass还是fail。这种以数据驱动的方式设计的自动化,不需要每一条命令都去写一条测试用例,减少代码量;同时增加测试内容也只需要修改外部文件而不需要修改代码,增加了可拓展性。

不过实际情况是,作为单元测试驱动的unittest:

无法给用例传参

不支持数据驱动

测试报告不够直观

原因是unittest在最初就是设计成只用来单元测试,不支持数据驱动的框架,引用Stack Overflow的一个回复:

“单元测试应该是独立的,没有依赖项的。这确保了每个用例都有非常具体而专一的测试反应。传入参数会破坏单元测试的这个属性,从而使它们在某种意义上无效。使用测试配置是最简单的方法,也是更合适的方法,因为单元测试不应该依赖外部信息来执行测试。那应该集成测试要做的。”

为了弥补unittest作为上层测试驱动框架的不足,感谢各路大神提供了第三方库,研究了重载unittest函数的方法。

4.2 HTMLReport,形成直观的测试报告

首先考虑的是最简单的部分,生成一份可视化的HTML形式的报告。在pypi.org上搜索“HTMLTestRunner”,实现网页报告的第三库有下面这些:

这些库实际上是用来替换unittest中TextTestRunner的部分,实际试用后,这里最终采用了HTMLReport 1.5.1这个库。以下面的方式加载:

这样便可以生成非常美观的测试报告了,如下图所示:

点击每条记录后面的pass、fail会显示详细的信息。

4.2 DDT,数据驱动测试执行

DDT本身即是数据驱动“Data-Driven Tests”的缩写。官方文档见:

http://ddt.readthedocs.io/en/latest/

能够实现数据驱动的第三方库还有paramunittest、parameterized等。

DDT包含类的装饰器ddt和两个方法装饰器data(直接输入测试数据)以及file_data(可以从json或者yaml中获取测试数据)

如果文件以”.yml”或者”.yaml”结尾,ddt会作为yaml类型处理,其他所有文件都会作为json文件处理。

首先准备好yaml文件,命名成IPMI.yaml,类似下图:

其中表示数据测试项目或者是用例名称;

表示具体执行命令,其中服务器管理ip、登录用户名、密码分别用hostname、username、password代替;

表示本用例执行完到执行下一条用例的间隔,默认为0秒;

表示用例是否要跳过,0表示不跳过,1表示跳过。

这种形式组织的yaml,在程序中以下面的方式进行解析。

解析后,形参会将测试数据以字典的形式返回。

每一组数据,就会形成一个测试用例,此时DDT命名的用例名称格式是test_IPMI_00001,在HTMLReport生成报告后展示为:

这样每条用例的测试项目显示不够直观,所以需要修改这两个第三方库的源代码。

ddt.py(这里保留了中间的index是为了用例执行时能够按序执行)

HTMLReport.py(需要解压缩工具打开后修改)

修改完成后,报告中的用例名称展示为:

4.3 传入参数到TestCase

虽然ddt支持数据驱动,但是测试数据中命令字符串包含的hostname、username和password是需要在测试前配置并替换的内容,这里希望能够通过传入参数重组命令的方式去执行,而不是在测试前修改yaml文档中每一条命令的每一个值。所以需要继承TestCase类并重载初始化函数以及改写用例加载过程函数。具体实现如下:

然后具体的测试用例类不再继承,而是继承自己实现的,这样子就能使用、和三个参数了

最后在调用时,实例化ParametrizedTestCase

5、最终实现

其实到4.4为止,就已经完成了整个IPMI协议测试框架的构建,运行程序后就可以直接进行IPMI协议的测试工作。不过为了满足不同使用者在不同电脑上都能够直接运行这个框架,这里通过pyqt5实现UI并用pyinstaller打包成exe程序,最终实现了配置过程不超过10秒的一键测试工具。

在界面中输入输入管理IP,用户与密码,点击一键测试后开始测试。同时打开的console界面会显示debug信息与测试过程。整个测试时长大约10分钟左右,中间不需要人为干预。

最后最关心的是,这样设计的IPMI协议测试是否能够检查出问题呢?实际拿超微和曙光的两款服务器进行了测试。结果可以发现一些在BMC界面测试中无法察觉的问题,像是获取fru信息返回结果正确,但命令响应却返回了失败;命令配置IP地址为同一值之后无法再获取IP相关的所有信息等问题。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180902G0VTYT00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

同媒体快讯

扫码关注云+社区

领取腾讯云代金券