
下午好,亲爱的读者们。 我叫维克多·布罗夫(Victor Burov)。 我在ISPsystem担任开发人员,并希望分享我在测试自动化方面的经验。
碰巧的是,手动测试在我国盛行,测试人员花费大量时间执行相同的操作。 一旦我们想到:为什么不教面板重复测试人员的操作,因为实际上它们都变成了特定的API调用。 这样一来,即使没有编程技巧,人们也可以编写测试。
我们决定编写一个用于创建自动测试的模块。 这样测试人员只需按下按钮即可创建测试,满足测试用例的条件,最后单击“完成”-就是这样,测试已经准备就绪! 一个简单的想法,但是要意识到这并不容易。 因为我们希望该模块最大程度地适应我们的产品并利用统一的界面:因此,录制的视频看起来像现成的测试用例。 这将完全消除编写测试的手动工作。 生成的系统称为“磁带记录器”。
查看测试用例条件的界面工作原理
所有请求参数(HTTP标头,环境变量,POST数据,如果有的话)和整个响应都写入xml文件。 每条记录都分配有一个序列号。 所有请求都分为修改和非修改。 记录测试后,许多非修改请求会被删除,因为它们不会影响测试的执行,而只会延迟和混淆执行过程(因此屏幕截图中缺少序列号)。
在录制过程中,磁带录音机允许您一键设置表格上的字段和列表列中的值。 它还允许您记录负面测试,记住面板在记录测试时返回的错误。
在回放过程中,不使用浏览器就将请求直接发送到应用程序API。
实际上,磁带录音机是一个模块,已集成到我们所有产品中,并允许您安装事件响应处理器。 使用COREmanager编写。
录音机记录一个呼叫的示例:
记录<params> <param name="CONTENT_LENGTH">210</param> <param name="CONTENT_TYPE">application%2Fx%2Dwww%2Dform%2Durlencoded%3B%20charset%3DUTF%2D8</param> <param name="HTTPS">on</param> <param name="HTTP_ACCEPT">text%2Fhtml%2C%20%2A%2F%2A%3B%20q%3D0%2E01</param> <param name="HTTP_ACCEPT_LANGUAGE">en%2DUS%2Cen%3Bq%3D0%2E5</param> <param name="HTTP_CACHE_CONTROL">no%2Dcache</param> <param name="HTTP_CONNECTION">keep%2Dalive</param> <param name="HTTP_COOKIE">corelang5%3Dorion%3Aru%3B%20ispmgrlang5%3Dorion%3Aru%3B%20ipmgrlang5%3Dorion%3Aru%3B%20ipmgrses5%3Dbdd69179d627%3B%20ispmgrses5%3D14157f7bbc5e%3B%20menupane%3D30%5Faccount%2D1%253A30%5Fdomains%2D1%253A30%5Fwebserver%2D1%253A30%5Fantispam%2D1%253A30%5Fmaintain%2D1%253A30%5Ftool%2D1%253A30%5Fstat%2D1%253A30%5Fsrvset%2D1%253A30%5Fsysstat%2D1%253A30%5Fintegration%2D1%253A30%5Fset%2D1%253A30%5Fmgrhelp%2D1</param> <param name="HTTP_HOST">172%2E31%2E240%2E175%3A1500</param> <param name="HTTP_ISP_CLIENT">Web%2Dinterface</param> <param name="HTTP_PRAGMA">no%2Dcache</param> <param name="HTTP_REFERER">https%3A%2F%2F172%2E31%2E240%2E175%3A1500%2Fispmgr</param> <param name="HTTP_USER_AGENT">Mozilla%2F5%2E0%20%28X11%3B%20Ubuntu%3B%20Linux%20x86%5F64%3B%20rv%3A24%2E0%29%20Gecko%2F20100101%20Firefox%2F24%2E0</param> <param name="HTTP_X_REQUESTED_WITH">XMLHttpRequest</param> <param name="QUERY_STRING"/> <param name="REMOTE_ADDR"></param> <param name="REMOTE_PORT">38640</param> <param name="REQUEST_METHOD">POST</param> <param name="REQUEST_URI">%2Fispmgr</param> <param name="SCRIPT_NAME">%2Fispmgr</param> <param name="SERVER_ADDR">172%2E31%2E240%2E175</param> <param name="SERVER_NAME">172%2E31%2E240%2E175</param> <param name="SERVER_PORT">1500</param> </params> <postdata>func%3Demaildomain%2Eedit%26elid%3D%26name%3Dtest%2Eemail%26owner%3Dusr%26ipsrc%3Dauto%26defaction%3Derror%26redirval%3D%26spamassassin%3Doff%26avcheck%3Doff%26clicked%5Fbutton%3Dok%26progressid%3Dfalse%5F1424243906672%26sok%3Dok%26sfrom%3Dajax%26operafake%3D1424243906673</postdata> <answer> <doc lang="ru" func="emaildomain.edit" binary="/ispmgr" host="https://172.31.240.175:1500" features="cba82687e7756e2c0195c88d4180f5d50" notify="0" theme="/manimg/orion/" css="main.css" logo="logo-ispmgr.png" logolink="" favicon="favicon-ispmgr.ico" localdir="default/"> <metadata name="emaildomain.edit" type="form" mgr="ispmgr" decorated="yes"> <form> <field name="name"> <input type="text" name="name" required="yes" check="domain" convert="punycode" maxlength="255"/> </field> // <buttons> <button name="ok" type="ok"/> <button name="cancel" type="cancel"/> </buttons> </form> </metadata> <messages name="emaildomain.edit" checked="cba82687e7756e2c0195c88d4180f5d5"> <msg name="currentmonth"> </msg> // </messages> <doc lang="ru" func="emaildomain.edit" binary="/ispmgr" host="https://172.31.240.175:1500" features="cba82687e7756e2c0195c88d4180f5d50" notify="0" theme="/manimg/orion/" css="main.css" logo="logo-ispmgr.png" logolink="" favicon="favicon-ispmgr.ico" localdir="default/"> <slist name="owner"> <val key="usr">usr</val> </slist> <slist name="defaction"> <val msg="yes" key="error"> </val> </slist> <slist name="ipsrc"> <val msg="yes" key="auto"> </val> </slist> <name/> <avcheck>off</avcheck> <owner>usr</owner> <ipsrc>auto</ipsrc> </doc> <id>test.email</id> <ok/> <tparams> <clicked_button>ok</clicked_button> </tparams> </doc> </answer> <localmacro> <macros name="mpre_HostIP" field="ipsrc">auto</macros> </localmacro>
改进(我们没有事先考虑的问题)
等待中
一个人可以“等一下”,一台计算机-不。 磁带录音机应该解决的第一个问题是迷信者的写作。 人们为了等待操作完成而没有发明的东西。 实现了对后台任务的期望以及在指定的步骤添加停止指定的秒数的能力。
记录和编辑测试步骤
可能每个人都记得打字机的时代:一个错误-您必须重新打印整个页面。

为了使测试人员不必用错误的动作重写整个测试,添加了一种机制来保存后从任何步骤重新记录测试。 当您需要使测试适应被测功能的行为变化时,便可以使用编辑功能。
变量宏
在测试期间,传输和检查的参数中的值根据运行它们的服务器而改变。 此类数据的一个示例是IP地址。 在测试记录阶段无法识别它们,因此我添加了一个宏系统。 这使我们能够创建与环境没有严格联系的测试。 该解决方案的缺点是在记录后必须手动指定宏。
使磁带录音机的工作大大复杂化的另一个问题是使用非本机键。 我们没有立即注意到它,因为我们在ISPmanager上测试了使用本机标识符的磁带录音机。 但是在其他一些面板中,记录由唯一的ID标识。 因此,我不仅要教磁带录音机在创建记录或对象后获得标识符(因为ID可以从头到尾更改),而且还要在以后的所有请求中替换它。
JUnit格式支持
磁带生成的测试在Jenkins持续集成环境中自动运行。 测试完成后,将创建一个xml文件,其中包含JUnit格式的数据。 为了正确地形成文件,引入了对测试命名的限制。 例如,测试User.Create.xml属于名称为User的测试套件,因此,它具有一个名为Create的测试用例。 如果发生错误,则将带有错误的完整描述的故障节点添加到该节点。
指标
唯一的被调用函数的数量是在测试通过期间计算的,并且比率是按函数总数的百分比确定的,其中不包括仅供内部使用的函数。 因此,可以测量最简单的测试覆盖率。 此外,度量标准还显示完成测试所需的总时间以及成功和失败测试的数量。
测试库
首先,测试库解决了将现成的测试转移到其他服务器的问题。 当几个测试人员编写测试时,它也很方便。 在我们的COREmanager的基础上,还开发并部署了一个用于存储存储测试的小面板。 录音机有一个模块,用于使测试与存储同步。 在编写新测试或从存储库中卸载测试时,它们会自动变得不可用于加载到存储库中,因此不会造成混乱。 更改测试后,将增加修订号,以便仅将更改后的测试加载到存储库中。
困难(嗯,没有困难的地方)
使用磁带录音机表明,并非所有API函数都遵循我们的内部建议。 特别是,并非所有函数在创建记录标识符后都返回记录标识符。 我必须返回,包括工作代码,并使其符合要求。
死锁是另一个问题。 该面板表示在独占模式下某些关键操作的执行。 磁带录音机是同一面板的一部分,导致此类功能导致整个系统死机。 只有在GDB的帮助下才能确定这一点(没人记得此功能)。 不幸的是,存在拐杖,因为是在运行磁带录音机的测试时决定以多线程模式执行这些功能的。 从理论上讲,可以设计一个不带模块但带一个单独面板的录音机。 但是我们没有尝试。
不幸的是,书写和忘记也失败了。 我们产品的界面正在发生变化,并且变得越来越复杂。 而且,接口组件的数量正在积极增长。 因此,磁带记录器必须不时进行修改,以便在出现新组件时,它可以分析其结构并处理查询结果。
结论
创建录音机有助于提高测试产品的质量。 为培训测试人员节省了时间和资源。 一路走来,录音机使我们能够审查API是否符合内部建议,因此使API更具“逻辑性”。