精华内容
下载资源
问答
  • 2021-09-26 10:50:43

    UI 测试是一种测试类型,也称为用户界面测试,通过该测试,我们检查应用程序的界面是否工作正常或是否存在任何妨碍用户行为且不符合书面规格的 BUG。了解用户将如何在用户和网站之间进行交互以执行 UI 测试至关重要,通过执行 UI 测试,测试人员将尝试模仿用户的行为,以查看用户将如何与程序进行交互,并查看网站的运行情况是否如预期的那样,是否有缺陷。

    在上次的自动化测试系列(二)中为大家大体介绍了API测试的概念及在猪齿鱼中的实践展开,本文主要围绕UI测试进行概念介绍及Choerodon中的实践展开。

    下面为大家详细介绍猪齿鱼提供的UI测试功能:

    什么是 UI 测试

    UI 测试涵盖了用户交互部分,包括用户关注的网站结构和视觉部分。Web 网站包含许多来自 CSS,JavaScript 和许多其他语言的不同 Web 元素,网站元素可以连接到屏幕、键盘、鼠标或用户用于与网站进行交互的任何其他工具,UI 测试则捕获这些元素并对其进行测试和声明。

    在执行 UI 测试时,需要注意确保应用程序不存在任何跨浏览器兼容性问题。由于每个浏览器都使用不同的浏览器引擎,并且可能不支持相同的 CSS 功能。因此,确保UI 在所有主要浏览器上无缝呈现非常重要。在不同的浏览器上进行测试称为跨浏览器测试,可以帮助测试人员在所有主要浏览器和设备(包括手机,平板电脑等)的多种组合下测试其网站。

    手动或自动,如何选择?

    与其他任何类型的测试一样,UI 测试也可以手动或通过自动化执行。手动测试要求测试人员在每个元素上手动执行每个测试。例如,测试输入字段将需要针对任何差异一次又一次地键入不同的值。如果网站 UI 的组件较少​​,则最好通过手动过程进行 UI 测试,快速地完成。但它不适合复杂的网站,用户界面丰富的网站使手动 UI 测试则非常低效,费时且容易出错。

    适合UI自动化测试的场景

    不是所有的测试场景都适合用自动化测试来实现,对此,可以参考以下的标准辅助判断:

    • 项目的需求不会频繁变动
    • 页面的 UI 已经进入稳定阶段
    • 项目周期足够长
    • 大量回归的测试任务

    其中,有些项目是明显不适合使用 UI 自动化测试的,例如视频播放器,音乐播放器等交动性强,并发依赖强的软件。

    UI自动化测试的优点

    UI自动化测试过程简化了创建UI测试、运行测试以及查看结果的过程,开发和测试团队选择自动化UI测试的原因有很多,最值得注意的包括:

    • 时间 – 手动测试速度很慢,无法与许多开发过程保持同步。
    • 成本 – 手动测试需要大量资源且成本很高。
    • 准确性 – 执行重复性任务时,手动测试容易出现更多错误。相反,自动化减少了这些错误的机会。
    • 规模化 – 执行复杂的迭代时,很难依靠手动测试。
    • 趋势 – 大多数组织已经意识到如何从自动化测试中受益,因此,跳上自动化潮流的压力越来越大。

    UI自动化测试设计原则

    • 一个测试用例完成一个功能点测试(常用):一个手工用例对应一个自动化测试用例;
    • 一个脚本是一个完整的场景;
    • 脚本之间独立,不能有依赖(脚本间相互隔离):例如与登陆状态相关的用例:个人中心、订单详情、下单购物等,如果脚本之间不独立,相互依赖,在登陆的测试脚本失败的情况下,会导致个人中心、订单详情、下单购物的测试脚本全军覆灭,后续修复与维护成本高;
    • 设置合适的检查点:通过断言判断用例的成功与否;
    • 设计良好的框架:Python 常用的测试框架有 unittest 与 pytest,利用框架,及对共用的测试模块进行封装,减少自动化测试脚本维护的工作量;

    WEB端UI测试工具介绍

    API测试用例主要由4个部分组成,分别是:用例的基础信息、前置步骤、请求脚本以及断言。

    UTF

    UTF( Unified Functional Testing) = QTP( Quick Test Pro) + ST( Service Test)由 HP 公司开发。它是一种企业级的自动测试工具,提供了强大易用的录制回放功能,同时兼容对象识别模式与图像识别模式两种识别方式,支持 B/S 与 C/S 两种架构的软件测试,是目前主流的自动化测试工具。主要是用于回归测试和同一软件的新版本测试。

    Robot Framework

    是一款基于 Python 语言编写的自动化测试框架,具备良好的可扩展性,支持关键字驱动,可以同时测试多种类型的客户端或者接口,可以进行分布式测试。

    Selenium

    Selenium概要

    Selenium 也是一个用于 Web 应用程序测试的工具,支持多平台、多浏览器、多语言去实现自动化测试,目前在 Web 自动化领域应用最为广泛。

    Selenium 是最广泛使用的开源 Web UI(用户界面)自动化测试套件之一,最初由杰森·哈金斯(Jason Huggins)于 2004 年开发,作为 Thought Works 的内部工具。Selenium 支持跨不同浏览器,平台和编程语言的自动化。

    Selenium功能特性

    • Selenium 是一个开源和可移植的 Web 测试框架。
    • Selenium IDE 为创作测试提供了回放和录制功能,而无需学习测试脚本语言。
    • 它可以被视为领先的基于云的测试平台,可帮助测试人员记录他们的操作并将其导出为可重复使用的脚本,并具有易于理解且易于使用的界面。
    • Selenium 支持各种操作系统,浏览器和编程语言。如下列表:
      • 编程语言: C# ,Java,Python,PHP,Ruby,Perl 和 JavaScript
      • 操作系统:Android,iOS,Windows,Linux,Mac,Solaris。
      • 浏览器:谷歌浏览器,Mozilla Firefox,Internet Explorer,Edge,Opera,Safari 等。
    • 它还支持并行测试执行,从而减少了时间并提高了测试效率。
    • Selenium 可以与 Ant 和 Maven 等框架集成,用于源代码编译。
    • Selenium 还可以与 TestNG 等测试框架集成,以进行应用程序测试和生成报告。
    • 与其他自动化测试工具相比,Selenium 需要的资源更少。
    • WebDriver API 已经尝试集于 Selenium 中,这是对 Selenium 进行的最重要的修改之一。
    • Selenium Web 驱动程序不需要服务器安装,测试脚本直接与浏览器交互。
    • Selenium 命令根据不同的类进行分类,使其更易于理解和实现。
    • Selenium Remote Control(RC)与 WebDriver API 一起被称为 Selenium 2.0。此版本旨在支持充满活力的网页和 Ajax。

    Selenium三大优点

    • 速度:时间是每家公司的主要资源,自动化测试可以节省很多时间。Selenium Automation 测试要求我们只编写一次测试,然后一次又一次地运行它们,而不会以不同的值和不同的方案进行任何干预。
    • 准确性:只要测试编写正确,Selenium Automation 测试就可以帮助我们正确执行测试。手动测试的主要缺点是容易发生人为错误。
    • 透明度:Selenium Automation 测试还有助于快速生成报告,并在测试完成后立即与团队共享。另一方面,手动测试需要时间来提取结果并手动报告结果以通过软件或手动生成报告。

    Choerodon UI测试

    安装

    若在Choerodon 中使用 UI 测试,需要先安装Selenium IDE 。

    Selenium IDE(集成开发环境)是 Selenium Suite 下的开源 Web 自动化测试工具。与 Selenium WebDriver 和 RC 不同,它不需要任何编程逻辑来编写其测试脚本,而只需记录与浏览器的交互以创建测试用例。之后,可以使用播放选项重新运行测试用例。 注意:Selenium IDE 仅作为 Firefox 和 Chrome 插件提供,它无法在 Firefox 和 Chrome 以外的浏览器上记录测试用例。记录的测试脚本也可以导出到 C#,Java,Ruby 或 Python 等编程语言。

    Firefox 浏览器

    Chrome 浏览器

    使用

    在 Chrome 浏览器上使用 Selenium IDE 录制与回放脚本

    1、打开 IDE,初始化界面如图:

    2、创建并开始录制,输入录制的 web 地址

    3、录制完成,右击测试用例,保存或导出。Selenium IDE 保存的都是.side 的单文件

    Choerodon 中的 UI 测试是通过 Selenium IDE 中录制生成的 side 文件导入系统中,在 UI 测试界面中生成对应的测试用例与步骤;而后便能直接执行对应的测试文件来对界面 UI 操作进行测试,可以直观的看到生成的测试报告。

    总结

    UI测试是软件测试周期的重要组成部分,是改善用户体验和客户满意度的重要驱动力,大多数最终用户更关心他们实际看到和触摸的内容。因此,这也是为什么UI或用户界面变得如此重要,从而进行UI测试的原因。


    本文由猪齿鱼技术团队原创,转载请注明出处:猪齿鱼官网

    更多相关内容
  •  本文章主要会讲解Python中pytest框架的讲解,介绍什么是pytest、为何要测试、为何使用以及参考和扩展等

    一、前言

      本文章主要会讲解Python中pytest框架的讲解,介绍什么是pytest、为何要测试、为何使用以及参考和扩展等,除此之外下方有系列文章的传送门,还在持续更新中,感兴趣的小伙伴也可以前往查看,话不多说,让我们一起看看吧~

    系列文章:
      系列文章1:【Python自动化测试1】遇见Python之美
      系列文章2:【Python自动化测试2】Python安装配置及PyCharm基本使用
      系列文章3:【Python自动化测试3】初识数据类型与基础语法
      系列文章4:【Python自动化测试4】字符串知识总结
      系列文章5:【Python自动化测试5】列表与元组知识总结
      系列文章6:【Python自动化测试6】字典与集合知识总结
      系列文章7:【Python自动化测试7】数据运算符知识合集
      系列文章8:【Python自动化测试8】流程控制语句讲解
      系列文章9:【Python自动化测试9】函数知识合集
      系列文章10:【Python自动化测试10】文件基础操作
      系列文章11:【Python自动化测试11】模块、包与路径知识合集
      系列文章12:【Python自动化测试12】异常处理机制知识合集
      系列文章13:【Python自动化测试13】类、对象、属性与方法知识合集
      系列文章14:【Python自动化测试14】Python自动化测试基础与进阶练习题
      系列文章15:【Python自动化测试15】unittest测试框架的核心概念与作用
      系列文章16:【Python自动化测试16】测试用例数据分离
      系列文章17:【Python自动化测试17】openpyxl二次封装与数据驱动
      系列文章18:【Python自动化测试18】配置文件解析与实际应用
      系列文章19:【Python自动化测试19】日志系统logging讲解
      系列文章20:【Python自动化测试20】接口自动化测试框架模型搭建
      系列文章21:【Python自动化测试21】接口自动化测试实战一_接口概念、项目简介及测试流程问答
      系列文章22:【Python自动化测试22】接口自动化测试实战二_接口框架修改及用例优化
      系列文章23:【Python自动化测试23】接口自动化测试实战三_动态参数化与数据伪造
      系列文章24:【Python自动化测试24】接口自动化测试实战四_Python操作数据库
      系列文章25:【Python自动化测试25】接口自动化测试实战五_数据库断言、接口关联及相关管理优化

      

    二、pytest讲解

    2.1 什么是pytest?

      pytest是一款单元测试框架,在编程过程中,单元主要指的是代码中最小的组成部分,例如函数或类,在面向对象中,最小的单元就是类下面的方法。
      当我们编写好一段程序后,会对这些函数和方法进行检测,是否出现程序错误,这种对程序的函数和方法进行测试的过程,就叫做单元测试。
      pytest的测试框架类似于unittest框架相似,但pytest的测试框架比unittest更加简洁、高效,具体的pytest的基础介绍,大家可以参考:pytest使用介绍

      

    2.2 为什么使用pytest?

      pytestunittest类似,但pytest还是有很多的优势:

    """
    pytest优势
    1、pytest能够兼容unittest,如果之前用例是unittest编写的,可以使用pytest直接进行使用
    2、pytest的断言直接使用assert断言,并非使用self.asert等语法语句以及其他各式各样的断言方式
    3、pytest对于失败的测试用例会提供非常详细的错误信息
    4、pytest可以自动发现并收集测试用例
    5、pytest有非常灵活的fixture管理
    6、pytest有mark标记机制,可以标记某些用例为冒烟测试用例
    7、pytest提供了非常丰富的插件系统
    8、pytest不需要写类,unittest是需要写类并继承的,这里pytest更加简洁
    """
    

      

    2.3 使用pytest

      安装pytest库后设置默认的运行器为pytest

    def test_add():
        assert True
    

    在这里插入图片描述
    在这里插入图片描述
      在之前的文章提到了,框架就意味着规则,pytest用例规则如下:

    """
    pytest用例规则:
    1、模块名称 test开头.py结尾,或者*_test.py
    2、测试用例函数的名称 def test_XXX()
    3、可以不定义测试类
    """
    
    """
    pytest的运行方式:
    1、pycharm当中的运行图标,pytest开头开头运行,如不是pytest可以在setting中查找pytest并设置成pytest运行器
    2、pytest命令行:要进入项目的根目录运行pytest命令,pytest命令会自动收集运行指令时候,所有子目录下符合要求的测试用例,例如test_login.py,模块且以test开头,函数test开头,类也是如此
    3、通过python包或者python模块运行
    
    """
    
    
    
    
    

      

    2.4 pytest的运行方式

      pytest有三种运行方式:

    """
    方式一:直接通过代码左侧的三角进行运行(pycharm)
    """
    
    """
    方式二:通过命令行运行 -- pytest -- html=output.html
    """
    
    """
    方式三:通过python运行
    """
    from datetime import datetime
    
    import pytest
    
    date_str = datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
    # 测试报告的名称
    report_name = date_str + "萌笑天的专属测试报告.html"
    
    pytest.main([f"--html={report}"])
    

      

    2.5 pytest高级特性

    2.5.1 pytest用例筛选

      我们都做过冒烟测试,也知道冒烟测试用例,pytest支持用例筛选,你可以在想要的用例上进行标记,以此来表示这是一个冒烟测试用例:

    import pytest
    
    # 格式为:@pytest.mark.自定义标记名
    @pytest.mark.smoke
    def test_True()
    	assert True
    
    @pytest.mark.smoke
    def test_False()
    	assert False
    

      我们可以给一个用例或多个用例附上单独的标记,但这样是无法运行的,我们需要先注册标记,新建一个pytest.ini的配置文件并进行配置:

    [pytest]
    markers = 
    	smoke
    

      注册完成后我们需要运行,在命令行输入pytest - m "smoke",这样就可以运行刚刚标记过的测试用例了,值得一提的是,如果这个标记是在函数上,那么就代表着函数属于标记的筛选用例,如果标记在类上,那么整个类下的所有函数都属于筛选用例,如例子所示,即全部为冒烟测试用例

    """
    用例筛选流程:
    1、需要在pytest.ini中注册标记的名称
    2、在测试用例函数或者测试用例类上面加上@pytest.mark.标记名
    3、运行指定标签 pytest -m "标记名"
    """
    

      如果运行多个标记那么可以继续在函数或类上再次进行新的标记,例如login标记,意味着我只想要执行登录模块的冒烟测试用例里,那么再次进行注册并运行即可,运行使用pytest -m "smoke and login",如果是冒烟测试用例和登录模块用例满足一个即可,那么就可以使用or即可,两者选其一,满足即运行

      

    2.5.2 pytest实现数据驱动

      pytest实现数据驱动可以使用unittest进行实现,也可以使用自己的ddt:

      注意:pytest参数化与unittest的参数化只能有一个,不能够共同使用

    """
    pytest使用unittest进行数据驱动的实现
    """
    import unittest
    imoort pytest
    from unittesetreport import ddt, list_data
    
    @pytest.mark.smoke
    @unittestreport.ddt
    class TestAddwithUnittest(unittest.TestCase):
    	
    	@unittestreport.list_data(["hello", "world", "mengxiaotian"])
    	def test_add_three(self, case_info):
    		aseert "萌笑天" in "最棒的萌笑天"
    	
    	def test_add_four(self):
    		assert "萌笑天" in "最棒的萌笑天"
    
    
    """
    使用自己的pytest实现
    """
    @pytest.mark.smoke
    @pytest.mark.login
    @pytest.mark.parametrize("case_info", ["hello", "world"])
    def test_add(case_info):
    	assert True
    
    

    2.5.3 pytest夹具

      pytest夹具会与unittest有一些不同,详见代码:

    def setup_function():
        """前置条件,每个测试用例之前"""
        print("hello, world!")
    
    def teardown_function():
        """后置条件,每个测试用例之后"""
    
    def test_hello():
        assert 520 == 1314
    
    def test_world():
        assert "萌" in "萌笑天"
    
    import pytest
    
    # 声明这是一个测试夹具
    @pytest.fixture()
    def connet_to_db():
        print("前置条件:正在连接数据库...")
        yield # 在yield前的都是前置
    # 清理动作
        print("后置清理,断开数据库连接...")
    
    @pytest.mark.usefixtures("connect_to_db")
    def test_mengxiaotian_love():
        assert 1314 == 1314
    

      

    2.6 allure下载

      万能百度搜索allure进入到GitHub下载或直接点击超链接:allure下载
      找到Download的字眼,并在其中点击releases
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
      

    2.7 pytest插件:allure-pytest安装与目录生成

      通过pip install allure-pytest进行安装
      生成报告在命令行中输入:pytest --alluredir=目录
      查看报告使用:allure serve 目录
      allure可以翻译成中文,具体这里不过多阐述如何查看报告数据,有兴趣的同学可以自行了解

    在这里插入图片描述

      

    2.8 unittest转pytest形式

      如果以代码形式呈现会比较复杂,笔者直接使用备注进行说明,大家如果之前的项目是unittest的项目那么可以根据本次说明转换成pytest

    """
    unittest转pytest:
    1、数据驱动的ddt换成pytest的标记形式
    2、unittest的testcase继承需要移除
    3、self.asserEqual 需要重新封装
    4、setUpclass 改成 pytest setup_class (参考上面的代码)
    """
    

      
      

      好啦~以上就是本次文章分享的全部内容啦,你学会了吗?希望能给大家带来帮助哦!

      
      

    在这里插入图片描述

    展开全文
  • 如何搭建自动化测试框架?

    千次阅读 2021-07-23 05:51:37
    序今天先聊聊如何搭建自动化测试框架,主要会聊聊一些思路上的东西,从一个最简单的demo到把一个框架该有的组件都搭建好。本文主要以web自动化为例子,使用的语言是js。一、什么是自动化测试框架在了解什么是自动化...

    今天先聊聊如何搭建自动化测试框架,主要会聊聊一些思路上的东西,从一个最简单的demo到把一个框架该有的组件都搭建好。本文主要以web自动化为例子,使用的语言是js。

    一、什么是自动化测试框架

    在了解什么是自动化测试框架之前,先了解一下什么叫框架?框架是整个或部分系统的可重用设计,表现为一组抽象构件及构件实例间交互的方法;另一种定义认为,框架是可被应用开发者定制的应用骨架。前者是从应用方面,而后者是从目的方面给出的定义。

    —百度百科

    对于自动化测试框架大致包含以下的内容

    自动化测试工具(selenium、puppeteer…)

    Runner(Jest…)

    日志(logger)

    报告(reportor)

    持续集成

    二、使用puppeteer开始写一个最小的demo

    2.1 工具选型

    目前,对于web端UI自动化比较主流的工具有selenium、webDriver。而在这里选择的是Puppeteer,先来了解下puppeteer。

    在chrome 59 chrome团队支持了headless模式,在Headless模式下,用于自动化测试和不需要可视化用户界面的服务器。例如,你想在一个网页上运行一些测试,从网页创建一个PDF,或者只是检查浏览器怎样递交URL。

    Puppeteer是谷歌官方出品的一个通过DevTools协议控制headless Chrome的Node库。可以通过Puppeteer的提供的api直接控制Chrome模拟大部分用户操作来进行UI Test或者作为爬虫访问页面来收集数据。

    Puppeteer 核心功能:

    利用网页生成PDF、图片

    爬取SPA应用,并生成预渲染内容(即“SSR” 服务端渲染)

    可以从网站抓取内容

    自动化表单提交、UI测试、键盘输入等

    帮你创建一个最新的自动化测试环境(chrome),可以直接在此运行测试用例

    捕获站点的时间线,以便追踪你的网站,帮助分析网站性能问题

    Puppeteer是使用node语言进行开发的,在使用中你可以使用async/await异步解决方案,async/await可能是目前为止最简单的异步方案了。

    很强大是不是,接着我们去学习下puppeteer的接口文档,接着开始写我们的demo。

    2.2 测试用例

    该用例是直接使用puppeteer,先launch一个browser然后newPage,接着开始写case。如果你想执行可以把then后的内容改成访问百度界面的。如下图,

    53470390529255b0c60cb4468dae1298.png

    7089ac7d63e97d767696370f3dd6368a.png

    我们来看下整体的框架,如下图,图中直接使用Puppeteer。

    对于以上的case,假如我要在写一条case,需要新建一个js文件,然后先launch一个browser然后newPage,接着开始写case。在这样的一个过程中我们可以看到我们每次都要launch browser,close browser,当然还有其他的问题比如怎么快速的执行多个用例等等,那么该如何解决这个问题呢?这时候考虑引入一个Runner的概念。

    三、使用Jest进行lifecycle管理

    3.1 了解Jest

    对于Jest,在并发执行可以保持最高的性能,在沙盒模式下每个测试都有一个干净的环境。Jest在做UT、AT有着很成功的应用。接着我们在框架里面加入lifecycle去管理一些资源。需要去做一些setup、teardown的工作。

    3.2 Jest+puppeteer

    在这里抽象了一个environment(下图左),去统一管理测试过程中的一些资源,在这里引入了setup、teardown,声明全局的browser、page变量。而对于case(下图右),使用Jest的case编写规则去写,首先是一个describe,类似test suite,在describe可以写多个it,一条的it代表一条的case,你就可以在一个文件里写多条的case。对比下二中的case此时我们不需要在每次执行launch了。

    6c2d7d2e32b1cfd06a5c25f93b6cb52e.png

    5b0844c7c535db4bb2f7d63b35820167.png

    接着看下整体的框架图,可以看到我们把jest给加入了

    对于上面的case我们把对页面元素的建模跟对应的操作以及测试方法都写到同个class里面了。对于这样的case,如果改动了某个元素需要改动很多个的文件,维护成本很高,这是我们不希望看到的。接下来我们引入了POM.

    四、POM

    4.1 了解POM

    POM的全称是Page Object Model。POM模型要求将一个页面上所有功能/可重用组件写到一个class文件中,它存在以下规则

    1.Page Object Model is a design pattern to create Object Repository for web UI elements.

    2.Under this model, for each web page in the application, there should be corresponding page class.

    3.This Page class will find the WebElements of that web page and also contains Page methods which perform operations on those WebElements.

    ce2f2712bbf26c6691c50a6ba32d9fbb.png

    对于三中的代码很明显没有遵循POM,接着我们进行改进。

    4.2 采用POM模型编写代码

    在这里我抽象出一个pages,把不同界面的建模以及对元素的操作放在一个文件夹下,在case层只有测试方法。

    3e610421b5233e6d741b5c4796efac22.png

    在使用POM的过程中我还做了一件事情,抽象出driver层,对puppeteer的API进行封装,封装的意义有两方面为了支撑其他的工具比如selenium,统一接口,更好的做兼容性测试,(puppeteer支持的browser类型比较少);另一方面,对于driver这类的工具在ui

    测试工程其实不会用到所有的大概能用上十几个method,我们可能对它进行封装让它更好用,比如可以在click前waitFor这个element出现,这样做就不需要在写case的时候每次click前都wait了。

    fd217ec645ed7a61031c337168fee641.png

    接着我们来看下整体的框架:

    ef2d0b2948a9ab8c94da4d29e2bbc95b.png

    对于这样的框架我们能很好的工作了,接下来需要加的是啥呢?

    五、Logger&Report&Assert

    loggger、report、assert是框架的必须部分。

    logger需要统一的管理打印到console或者某个file,logger可以帮助我们更快的定义问题对于logger可以分为两类一个应用本身的log,一个测试代码的log,根据需要收集;report是在测试结束后把结果展示出来,可以是dashboars可以是html,告诉case的整体情况,以及错误时候的信息,当然还可以是更详细信息,比如说每一步的步骤信息等;Assert这里是采用了Jest自带了,觉得Jset自带的expect已经很强大了,当然如果你需要用别的或者封装也是可以的。我们接着看下代码(如下图),对于这些我们去使用是很方便的集成的,易于集成也是衡量一个第三方library的一个重要指标。

    78c689a1fa9b0d6e24ff708c03cf8dcd.png

    f5d5bd0933ce0515d6bf4074e9cecfe5.png

    对于框架层加了两个模块进去。

    六、其他工具类

    54b91fb449d65793d82e0c2a58cc8f03.png

    对于框架而言,通常会留有一个Helper,把跟测试相关的工具放在这里。方便使用与管理。我们看下框架。

    七、持续集成

    首先,持续集成的目的,就是让产品可以快速迭代,同时还能保持高质量。它的核心措施是,代码集成到主干之前,必须通过自动化测试。只要有一个测试用例失败,就不能集成。自动化测试作为在持续集成中的一个重要环节,我们需要在执行UT/IT后接着执行AT,更早的发现bug。在这里可以使用docker搭建puppeter的运行环境,在jenkins上通过pipeline在docker中执行测试。

    八、总结

    以上就是本文的主要内容,希望看完这篇文章大家可以思考一些问题,对于这样的设计有何优化建议?在编写测试中可能会遇到哪些问题?我来说下,如何支持多个browser?测试太慢如何通过缓存提供web 元素的加载?面对这些该如何解决呢?

    最后的最后,该框架的源码在github上,https://github.com/summergan/EndToEnd

    目前只有文章中的demo,后续有时间会继续更新。

    版权声明:本文出自51Testing原创,51Testing软件测试网及相关内容提供者拥有内容的全部版权,未经明确的书面许可,任何人或单位不得对本网站内容复制、转载或进行镜像,否则将追究法律责任。

    展开全文
  • pytest接口自动化测试框架搭建

    千次阅读 2022-03-04 20:52:09
    文章目录 目录 ...最近自己也抽时间梳理了一份pytest接口自动化测试框架,因此准备写文章记录一下,做到尽量简单通俗易懂,当然前提是基本的python基础已经掌握了。如果能够对新学习这个框架的同

    文章目录

    目录

    一. 背景

    二. 基础环境

    三. 项目结构

    四、框架解析

    4.1 接口数据文件处理

    4.2 封装测试工具类

    4.3 测试用例代码编写

    4.4 测试用例运行生成报告


    一. 背景

    Pytest目前已经成为Python系自动化测试必学必备的一个框架,网上也有很多的文章讲述相关的知识。最近自己也抽时间梳理了一份pytest接口自动化测试框架,因此准备写文章记录一下,做到尽量简单通俗易懂,当然前提是基本的python基础已经掌握了。如果能够对新学习这个框架的同学起到一些帮助,那就更好了~

    二. 基础环境

    语言:python 3.8
    编译器:pycharm
    基础:具备python编程基础
    框架:pytest+requests+allure

    三. 项目结构

    项目结构图如下:
     

    每一层具体的含义如下图:


     

    测试报告如下图:

    四、框架解析

    4.1 接口数据文件处理


    框架中使用草料二维码的get和post接口用于demo测试,比如:
    get接口:https://cli.im/qrcode/getDefaultComponentMsg
    返回值:{“code”:1,“msg”:"",“data”:{xxxxx}}

    数据文件这里选择使用Json格式,文件内容格式如下,test_http_get_data.json:​​​​​​​

    {  "dataItem": [    {      "id": "testget-1",      "name": "草料二维码get接口1",      "headers":{        "Accept-Encoding":"gzip, deflate, br"      },      "url":"/qrcode/getDefaultComponentMsg",      "method":"get",      "expectdata": {        "code": "1"      }    },
        {      "id": "testget-2",      "name": "草料二维码get接口2",      "headers":{},      "url":"/qrcode/getDefaultComponentMsg",      "method":"get",      "expectdata": {        "code": "1"      }    }  ]}

    表示dataitem下有两条case,每条case里面声明了id, name, header, url, method, expectdata。如果是post请求的话,case中会多一个parameters表示入参,如下:​​​​​​​

    {    "id":"testpost-1",    "name":"草料二维码post接口1",    "url":"/Apis/QrCode/saveStatic",    "headers":{        "Content-Type":"application/x-www-form-urlencoded",        "Accept-Encoding":"gzip, deflate, br"    },    "parameters":{        "info":11111,        "content":11111,        "level":"H",        "size":500,        "fgcolor":"#000000",        "bggcolor":"#FFFFFF",        "transparent":"false",        "type":"text",        "codeimg":1    },    "expectdata":{        "status":"1",        "qrtype":"static"    }}

    为了方便一套脚本用于不同的环境运行,不用换了环境后挨个儿去改数据文件,比如
    测试环境URL为:https://testcli.im/qrcode/getDefaultComponentMsg
    生产环境URL为:https://cli.im/qrcode/getDefaultComponentMsg
    因此数据文件中url只填写后半段,不填域名。然后config》global_config.py下设置全局变量来定义域名:

    # 配置HTTP接口的域名,方便一套脚本用于多套环境运行时,只需要改这里的全局配置就OKCAOLIAO_HTTP_POST_HOST = "https://cli.im"CAOLIAO_HTTP_GET_HOST = "https://nc.cli.im"

    utils文件夹下,创建工具类文件:read_jsonfile_utils.py, 用于读取json文件内容:

    ​​​​​​​

    import jsonimport os
    
    class ReadJsonFileUtils:    def __init__(self, file_name):        self.file_name = file_name        self.data = self.get_data()
        def get_data(self):        fp = open(self.file_name,encoding='utf-8')        data = json.load(fp)        fp.close()        return data
        def get_value(self, id):        return self.data[id]
        @staticmethod    def get_data_path(folder, fileName):        BASE_PATH = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))        data_file_path = os.path.join(BASE_PATH, folder, fileName)        return data_file_path
    if __name__ == '__main__':    opers = ReadJsonFileUtils("..\\resources\\test_http_post_data.json")    #读取文件中的dataItem,是一个list列表,list列表中包含多个字典    dataitem=opers.get_value('dataItem')    print(dataitem)

    运行结果如下:

    4.2 封装测试工具类

    utils文件夹下,
    除了上面提到的读取Json文件工具类:read_jsonfile_utils.py,
    还有封装request 请求的工具类:http_utils.py
    从Excel文件中读取数据的工具类:get_excel_data_utils.py(虽然本次框架中暂时未采用存放接口数据到Excel中,但也写了个工具类,需要的时候可以用)


    http_utils.py内容:

    ​​​​​​​

    import requestsimport json
    
    class HttpUtils:    @staticmethod    def http_post(headers, url, parameters):        print("接口请求url:" + url)        print("接口请求headers:" + json.dumps(headers))        print("接口请求parameters:" + json.dumps(parameters))        res = requests.post(url, data=parameters, headers=headers)        print("接口返回结果:"+res.text)        if res.status_code != 200:            raise Exception(u"请求异常")        result = json.loads(res.text)        return result
        @staticmethod    def http_get(headers, url):        req_headers = json.dumps(headers)        print("接口请求url:" + url)        print("接口请求headers:" + req_headers)        res = requests.get(url, headers=headers)        print("接口返回结果:" + res.text)        if res.status_code != 200:            raise Exception(u"请求异常")        result = json.loads(res.text)        return result

    get_excel_data_utils.py内容:

    ​​​​​​​

    import xlrdfrom xlrd import xldate_as_tupleimport datetime
    
    class ExcelData(object):    '''    xlrd中单元格的数据类型    数字一律按浮点型输出,日期输出成一串小数,布尔型输出0或1,所以我们必须在程序中做判断处理转换    成我们想要的数据类型    0 empty,1 string, 2 number, 3 date, 4 boolean, 5 error    '''    def __init__(self, data_path, sheetname="Sheet1"):        #定义一个属性接收文件路径        self.data_path = data_path        # 定义一个属性接收工作表名称        self.sheetname = sheetname        # 使用xlrd模块打开excel表读取数据        self.data = xlrd.open_workbook(self.data_path)        # 根据工作表的名称获取工作表中的内容        self.table = self.data.sheet_by_name(self.sheetname)        # 根据工作表的索引获取工作表的内容        # self.table = self.data.sheet_by_name(0)        # 获取第一行所有内容,如果括号中1就是第二行,这点跟列表索引类似        self.keys = self.table.row_values(0)        # 获取工作表的有效行数        self.rowNum = self.table.nrows        # 获取工作表的有效列数        self.colNum = self.table.ncols
        # 定义一个读取excel表的方法    def readExcel(self):        # 定义一个空列表        datas = []        for i in range(1, self.rowNum):            # 定义一个空字典            sheet_data = {}            for j in range(self.colNum):                # 获取单元格数据类型                c_type = self.table.cell(i,j).ctype                # 获取单元格数据                c_cell = self.table.cell_value(i, j)                if c_type == 2 and c_cell % 1 == 0:  # 如果是整形                    c_cell = int(c_cell)                elif c_type == 3:                    # 转成datetime对象                    date = datetime.datetime(*xldate_as_tuple(c_cell, 0))                    c_cell = date.strftime('%Y/%d/%m %H:%M:%S')                elif c_type == 4:                    c_cell = True if c_cell == 1 else False                sheet_data[self.keys[j]] = c_cell                # 循环每一个有效的单元格,将字段与值对应存储到字典中                # 字典的key就是excel表中每列第一行的字段                # sheet_data[self.keys[j]] = self.table.row_values(i)[j]            # 再将字典追加到列表中            datas.append(sheet_data)        # 返回从excel中获取到的数据:以列表存字典的形式返回        return datas
    
    if __name__ == "__main__":    data_path = "..\\resources\\test_http_data.xls"    sheetname = "Sheet1"    get_data = ExcelData(data_path, sheetname)    datas = get_data.readExcel()    print(datas)

    4.3 测试用例代码编写

    testcases文件夹下编写测试用例:


    test_caoliao_http_get_interface.py内容:

    ​​​​​​​

    import loggingimport allure
    import pytestfrom utils.http_utils import HttpUtilsfrom utils.read_jsonfile_utils import ReadJsonFileUtilsfrom config.global_config import CAOLIAO_HTTP_GET_HOST
    
    @pytest.mark.httptest@allure.feature("草料二维码get请求测试")class TestHttpInterface:    # 获取文件相对路径    data_file_path = ReadJsonFileUtils.get_data_path("resources", "test_http_get_data.json")    # 读取测试数据文件    param_data = ReadJsonFileUtils(data_file_path)    data_item = param_data.get_value('dataItem')  # 是一个list列表,list列表中包含多个字典
        """    @pytest.mark.parametrize是数据驱动;    data_item列表中有几个字典,就运行几次case    ids是用于自定义用例的名称
        """
        @pytest.mark.parametrize("args", data_item, ids=['测试草料二维码get接口1', '测试草料二维码get接口2'])    def test_caoliao_get_demo(self, args, login_test):        # 打印用例ID和名称到报告中显示        print("用例ID:{}".format(args['id']))        print("用例名称:{}".format(args['name']))        print("测试conftest传值:{}".format(login_test))        logging.info("测试开始啦~~~~~~~")        res = HttpUtils.http_get(args['headers'], CAOLIAO_HTTP_GET_HOST+args['url'])        # assert断言,判断接口是否返回期望的结果数据        assert str(res.get('code')) == str(args['expectdata']['code']), "接口返回status值不等于预期"

    test_caoliao_http_post_interface.py内容:

    ​​​​​​​

    import pytestimport loggingimport allurefrom utils.http_utils import HttpUtilsfrom utils.read_jsonfile_utils import ReadJsonFileUtilsfrom config.global_config import CAOLIAO_HTTP_POST_HOST
    # pytest.ini文件中要添加markers = httptest,不然会有warning,说这个Mark有问题@pytest.mark.httptest@allure.feature("草料二维码post请求测试")class TestHttpInterface:    # 获取文件相对路径    data_file_path = ReadJsonFileUtils.get_data_path("resources", "test_http_post_data.json")    # 读取测试数据文件    param_data = ReadJsonFileUtils(data_file_path)    data_item = param_data.get_value('dataItem') #是一个list列表,list列表中包含多个字典
        """    @pytest.mark.parametrize是数据驱动;    data_item列表中有几个字典,就运行几次case    ids是用于自定义用例的名称        """    @pytest.mark.parametrize("args", data_item, ids=['测试草料二维码post接口1','测试草料二维码post接口2'])    def test_caoliao_post_demo(self, args):        # 打印用例ID和名称到报告中显示        print("用例ID:{}".format(args['id']))        print("用例名称:{}".format(args['name']))        logging.info("测试开始啦~~~~~~~")        res = HttpUtils.http_post(args['headers'], CAOLIAO_HTTP_POST_HOST+args['url'], args['parameters'])        # assert断言,判断接口是否返回期望的结果数据        assert str(res.get('status')) == str(args['expectdata']['status']), "接口返回status值不等于预期"        assert str(res.get('data').get('qrtype')) == str(args['expectdata']['qrtype']), "接口返回qrtype值不等于预期"

    企业中的系统接口,通常都有认证,需要先登录获取token,后续接口调用时都需要认证token。因此框架需要能处理在运行用例前置和后置做一些动作,所以这里用到了conftest.py文件,内容如下:

    ​​​​​​​

    import loggingimport traceback
    import pytestimport requests
    """如果用例执行前需要先登录获取token值,就要用到conftest.py文件了作用:conftest.py 配置里可以实现数据共享,不需要import导入 conftest.py,pytest用例会自动查找scope参数为session,那么所有的测试文件执行前执行一次scope参数为module,那么每一个测试文件执行前都会执行一次conftest文件中的fixturescope参数为class,那么每一个测试文件中的测试类执行前都会执行一次conftest文件中的fixturescope参数为function,那么所有文件的测试用例执行前都会执行一次conftest文件中的fixture
    """
    # 获取到登录请求返回的ticket值,@pytest.fixture装饰后,testcase文件中直接使用函数名"login_ticket"即可得到ticket值@pytest.fixture(scope="session")def login_ticket():    header = {        'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'    }    params = {        "loginId": "username",        "pwd": "password",    }    url = 'http://testxxxxx.xx.com/doLogin'    logging.info('开始调用登录接口:{}'.format(url))    res = requests.post(url, data=params, headers=header, verify=False)  # verify:忽略https的认证    try:        ticket = res.headers['Set-Cookie']    except Exception as ex:        logging.error('登录失败!接口返回:{}'.format(res.text))        traceback.print_tb(ex)    logging.info('登录成功,ticket值为:{}'.format(ticket))    return ticket
    #测试一下conftest.py文件和fixture的作用@pytest.fixture(scope="session")def login_test():    print("运行用例前先登录!")
        # 使用yield关键字实现后置操作,如果上面的前置操作要返回值,在yield后面加上要返回的值    # 也就是yield既可以实现后置,又可以起到return返回值的作用    yield "runBeforeTestCase"    print("运行用例后退出登录!")

    由于用例中用到了@pytest.mark.httptest给用例打标,因此需要创建pytest.ini文件,并在里面添加markers = httptest,不然会有warning,说这个Mark有问题。并且用例中用到的日志打印logging模板也需要在pytest.ini文件中增加日志配置。pytest.ini文件内容如下:

    [pytest]markers =  httptest: run http interface test  dubbotest: run dubbo interface test
    log_cli = truelog_cli_level = INFOlog_cli_format = %(asctime)s - %(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)slog_cli_date_format=%Y-%m-%d %H:%M:%Slog_format = %(asctime)s - %(pathname)s[line:%(lineno)d] - %(levelname)4s: %(message)slog_date_format=%Y-%m-%d %H:%M:%S

    4.4 测试用例运行生成报告

    ​​​​​​​

    运行方式:Terminal窗口,进入到testcases目录下,执行命令:
    运行某一条case:pytest test_caoliao_http_post_interface.py运行所有case: pytest运行指定标签的case:pytest -m httptest
    运行并打印过程中的详细信息:pytest -s test_caoliao_http_post_interface.py运行并生成pytest-html报告:pytest test_caoliao_http_post_interface.py --html=../testoutput/report.html运行并生成allure测试报告:1. 先清除掉testoutput/result文件夹下的所有文件2. 运行case,生成allure文件:pytest --alluredir ../testoutput/result3. 根据文件生成allure报告:allure generate ../testoutput/result/ -o ../testoutput/report/ --clean4. 如果不是在pycharm中打开,而是直接到report目录下打开index.html文件打开的报告无法展示数据,需要双击generateAllureReport.bat生成allure报告;

    pytest-html报告:


    generateAllureReport.bat文件位置:

    文件内容:

    allure open report/

    Allure报告:


    框架中用到的一些细节知识点和问题,如:

    conftest.py和@pytest.fixture()结合使用
    pytest中使用logging打印日志
    python中获取文件相对路径的方式
    python虚拟环境
    pytest框架下Allure的使用

    我会在后续写文章再介绍。另外框架同样适用于dubbo接口的自动化测试,只需要添加python调用dubbo的工具类即可,后续也会写文章专门介绍。

    参考建议
    好了 学习也就到此结束了 想了解更多相关知识请关注我吧!下面是小编想对读者大大们写的一封信哦! 记住要认真读哦!

     感谢每一个认真阅读我文章的人,看着粉丝一路的上涨和关注,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接免费拿走:
       ————————————————
    「学习资料 笔记 工具 文档领取」

     扫描二维码,
    备注“csdn999”
    小姐姐邀你一起学习哦~~
    和志同道合的测试小伙伴一起讨论测试技术吧!


       一定一定一定 要备注暗号:CSDN999
       ————————————————

     

     

    展开全文
  • 第一章:自动化测试基础 第一节 软件测试分类 关于软件测试领域名词颇多,发现有许多测试新手混淆概念,从不同的角度可以将软件测试有不同的分类的方法;所以,这里汇总常见软件测试的相关名词,对软件测试领域有个...
  • 我如何搭建自动化测试框架_01配置

    千次阅读 2022-03-22 20:38:56
    自动化测试框架
  • 单元测试之自动化测试工具

    千次阅读 2022-03-02 10:45:08
    何为单元测试(unit test) 单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。 其实,对“单元”的定义取决于自己。如果你正在使用函数式编程,一个单元最有可能指的是一个函数。你的单元测试...
  • (最新整理)自动化测试脚本编写规范

    千次阅读 2021-07-23 06:20:09
    《(最新整理)自动化测试脚本编写规范》由会员分享,可在线阅读,更多相关《(最新整理)自动化测试脚本编写规范(4页珍藏版)》请在人人文库网上搜索。1、完整)自动化测试脚本编写规范(完整)自动化测试脚本编写规范编辑...
  • 自动化测试和测试自动化的区别

    千次阅读 2020-04-07 17:16:05
    这是两个很绕口的词。而且乍一看起来好像就是同一份工作。今儿聊聊我个人对于这两者的认识。  举例:  有一天,一家手机公司要做一个UI自动化测试,于是他们聘请了一名工程师。  这个工程师...
  • selenium 自动化测试

    万次阅读 2022-01-26 15:27:18
    Selenium 是一个用于Web应用程序测试的工具,支持多平台、多浏览器、多语言去实现自动化测试。目前在Web自动化领域应用越来越广泛。
  • 自动化测试(一)

    千次阅读 2022-04-11 11:28:18
    web自动化测试工具 : QTP一个商业化的功能测试工具,收费,支持web,桌面自动化测试。 selenium 是一个开源的web自动化测试工具,免费,主要做功能测试。 开源软件,开源与免费的区别 跨平台,linux,Windows,...
  • 如题,本文附有仓库地址以及代码,目录如下: 1.下载安装appium 2.配置并使用appium 3.配置工程 一、下载安装appium 下载appium-desktop的...UI Automator,google提供的自动化测试框架,appium集成了它。详细了解
  • 目前最主流的自动化测试技术selenium

    千次阅读 2021-08-05 09:45:04
    主流的自动化测试工具是QTP,现在已经淘汰。2014年左右selenium开始兴起。 自动化功能测试主要是用于提升测试人员工作效率,70%的工作时间都是在进行UI测试。全部都是基于前端界面来实现的交互性操作。尽可能实现...
  • UFT自动化测试

    万次阅读 2021-06-24 21:34:47
    自动化测试 静态自动化:代码检测,类似于编译工具的编译系统 动态自动化: 基于浏览器和DOM对象的自动化:selemnium,Selenium测试直接运行在浏览器中,就像真正的用户在操作一样。支持的浏览器包括IE(7、8、9)...
  • Java接口自动化测试框架

    千次阅读 2019-11-21 12:07:20
    自动化测试框架 1. 测试框架TestNG 1.1 适合测试人员使用的原因 (1)比Junit涵盖功能更全面的测试框架 (2)Junit更适合隔离性比较强的单元测试 (3)TestNG更适合复杂的集成测试 1.2 基本介绍 (1)基本注解:...
  • ​ Selenium也是一个用于Web应用程序测试的工具,支持多平台,多浏览器,多语言去实现自动化测试,目前在Web自动化领域应用越来越广泛 什么是Selenium? Selenium是专门为Web应用程序编写的一个自动化验收程序工具。...
  • EasyClick IOS自动化测试分布式部署

    千次阅读 2022-04-03 14:08:19
    EasyClick IOS自动化测试分布式部署
  • 二、什么是自动化测试框架 三、非PO模式和PO模式优缺点对比 四、如何从0到1搭建PO模型 五、自动化测试框架和PO的关系 六、总结 一、什么是PO模式 全称:page object model 简称:POM/PO PO模式最核心的思想是...
  • 一、前言 最近有童鞋和我抱怨,说很...当我第一次知道自动化测试的时候,除了知道“自动化”这三个有些高大上的称呼之外,我对自动化测试一无所知,正如谈恋爱一样,找女朋友之前要知道她是谁。 自动测试就是把以...
  • 自动化测试面试题(一)

    千次阅读 2020-08-27 21:41:20
    自动化测试面试题(一)NO.1 什么是自动化测试NO.2 什么是分层测试?NO.3 自动化测试的适用和不适用场景NO.4 你觉得自动化测试最大的缺陷是什么?NO.4 项目使用的自动化测试框架NO.5 对库的使用NO.6 如何设计高质量...
  • 几款Android 应用自动化测试工具

    万次阅读 多人点赞 2018-01-09 10:20:47
    本文介绍几款流行的 Android应用自动化测试工具。 Monkey测试:随机测试,压力测试,运行在模拟器或实际设备中。 MonkeyRunner测试:操作简单,可录制测试脚本,可视化操作,主要生成坐标的自动化操作,移植性不...
  • 题记:本文简述如何利用appium对Windows桌面应用程序进行UI自动化测试。所谓UI自动化测试,就是模拟一个用户,对应用程序的UI进行操作,以完成特定场景的功能性集成测试。要对Windows桌面应用程序进行UI自动化测试,...
  • pytest自动化测试框架简介

    千次阅读 2021-05-29 23:33:45
    pytest自动化测试框架简介 总所周总结,自动化测试的快速运行,都离不开框架,有了合适的框架,自动化代码程序的管理和使用,真的真的真的 方便太多了,接下来介绍一款拥有众多插件,公司主流使用的框架 ———...
  • python+selenium自动化测试-1概述

    千次阅读 2019-09-09 14:23:27
    用selenium自动化测试差不多两年了,有很多心得体会。在回归测试方面,selenium有很大的优势,一定程度上代替了手工测试,让我有更多时间关注项目业务和其他测试技术。 学自动化技术至少要掌握以下几个步骤:获取...
  • 如果你对此文有任何疑问,如果你也需要接口项目实战,如果你对软件测试、接口测试、自动化测试、面试经验交流感兴趣欢迎加入Python自动化测试技术群:953306497群里的免费资料都是笔者十多年测试生涯的精华。...
  • 2w字的超级大文:自动化测试入门

    千次阅读 2022-02-16 16:09:12
    2.1 前端自动化测试产生的背景及原理 2.2 分组、用例 2.3 matchers匹配器 2.4 测试操作节点方法 2.5 Jest常用命令 3.Jest进阶使用 1.1 异步函数的测试 4.Jest中的mock 4.1 模拟函数jest.fn() 4.2 模拟文件...
  • 是一个开源的web自动化测试的框架,支持多种编程语言,支持跨浏览器平台进行测试。 Selenium 1.0或Selenium RC Selenium 2.0或Selenium Webdriver Selenium 3.0 问题2:你如何从命令行启动Selenium RC? java -jar ...
  • 初级自动化测试笔试题

    千次阅读 2019-07-31 17:14:12
    D、RF引用AppiumLibrary后可以做移动端界面自动化测试 13、当Android页面刷新一帧的时间明显超过多少时,页面就可能卡顿现象?() A、10ms B、16ms C、60ms D、128ms 14、以下关于HTML文档的说法正确的是...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 306,034
精华内容 122,413
关键字:

自动化测试的声明

友情链接: K60P144M100SF2V2RM.rar