从零开始搭建自动化测试工具:详细指南(一)

在当今快速发展的软件行业中,自动化测试已经成为了保证软件质量、提高开发效率的关键环节。而开发一款适合自己项目需求的自动化测试工具,更是能为测试工作带来极大的便利和效率提升。本文将手把手教你开发一款简单的自动化测试工具,让你轻松应对日常的测试任务。

明确需求与目标

在开始开发自动化测试工具之前,我们首先需要明确项目的具体需求以及工具要实现的目标。例如,我们要为一个Web应用开发自动化测试工具,目标可能是实现对页面元素的自动操作、表单提交验证、页面跳转检测以及结果断言等功能。又或者我们对应用的接口层进行测试。明确需求和目标有助于我们在后续的开发过程中保持清晰的思路,确保工具的实用性和有效性。

不管我们是要进行UI自动换测试、接口自动化测试或者性能自动化测试。首先我们要明确我们工具的大致方向。设计出工具的主要功能和架构,我们才能进行后面的工作。

我们还需要确定工具的使用者,根据使用者的技能或者工具设计的初衷,来明确工具怎么实现自动化测试脚本的编写。例如,我们可以通过直接编写代码的方式来实现自动化测试,但是对于测试人员有一定的技术及编码能力的要求,也可以通过一些低代码架构,或者引入前端页面的方式,来降低对测试人员的技术和编码能力要求,更简便的实现自动化脚本的编写或者自动生成。

选择合适的编程语言和框架

编程语言

Python是一种非常流行且适合自动化测试开发的编程语言,它具有简洁易读的语法、丰富的库和框架以及强大的社区支持。因此,在本教程中我们将选择Python作为开发语言。

测试框架

对于Web应用的自动化测试,Selenium是一个广泛使用的强大框架。它提供了对各种浏览器的自动化操作支持,能够模拟用户在浏览器中的行为,如点击、输入、导航等。我们将使用Selenium与Python结合来开发我们的自动化测试工具。

对于接口的自动化测试,requests是一个简单且优雅的Python HTTP库,它使得发送HTTP请求变得异常简单。

对于低代码方式编写脚本,Pytest-BDD是一个强大的BDD测试库,它通过提供Gherkin语言支持和一系列装饰器,使得开发者能够以更自然、更易于理解的方式编写和执行BDD测试。结合Pytest测试框架的灵活性和强大的断言功能,Pytest-BDD为自动化测试过程带来了更高的效率和更好的可维护性。

首先,确保你已经安装了Python和Selenium、Requests、Pytest-BDD。可以通过以下命令安装Selenium:

pip install selenium requests pytest-bdd

设计工具架构

一个好的工具架构能够提高代码的可维护性和扩展性。对于我们的自动化测试工具,我们可以考虑以下基本架构:

测试用例层

负责组织和编写测试用例,使用行为驱动测试,编写用例步骤,Pytest-BDD对于UI或者接口测试都是通用的。

动作封装

将页面上的各种元素(如按钮、输入框、链接等)的操作方法进行封装。将接口请求的各种方式和传参类型进行封装。这样我们的用例可以专注于写操作逻辑,不用考虑每个执行动作的处理。

断言封装

封装断言方法,实现通用的断言方法。

测试报告生成层
在测试执行完成后,生成详细的测试报告,包括测试结果、执行时间、错误信息等。可以使用第三方库如HTMLTestRunner来生成HTML格式的测试报告,方便查看和分析。

编写测试用例

接下来,我们编写测试用例,使用Pytest-BDD来进行用例编写:

UI测试用例:

Feature: UI自动化测试

    Scenario: 登录测试
        Given 初始化
        When 在元素"用户名输入框"中,输入账号"admin"
        When 在元素"密码输入框"中,输入密码"123456"
        When 点击"登录按钮"
        When 校验元素"提示框"校验是否包含文本"登录成功"
        Then 退出

接口测试用例:

Feature: 登录接口测试

    Scenario: 登录接口测试
        Given 初始化
        When 调用"/login"接口
        When 使用"post"请求    # post get put delete
        When 参数类型"json"    # params json form-data form-urlencoded
        When 请求头"{'Content-Type': 'application/json'}"
        When 参数"{'username': 'test', 'password': 'password'}"
        When 校验类型"包含校验"    # 校验类型: 包含校验 相等校验 字段类型校验 字段值校验 状态码校验
        When 校验文本"成功"
        When 校验字段类型为"str"    # 字段类型:int str float list dict bool
        When 校验字段"loginInfo"
        Then 接口调用成功

上面的用例,结构清晰,易于理解。对于没有编码经验的测试人员,也可以很快的写出自动化测试用例。

实现动作封装

UI自动化操作动作

使用Python来实现上面的测试步骤

import pytest
from pytest_bdd import given, parsers, when, then
from selenium.webdriver.chrome import webdriver
import elements


class SeleniumTool():
    def __init__(self):
        self.options = webdriver.ChromeOptions()
        self.options.add_argument("--start-maximized")
        self.driver = webdriver.Chrome(options=self.options)
        self.driver.get('http://172.17.0.1/login')


@pytest.fixture
@given('初始化')
def selenium_tool():
    return SeleniumTool()


@when(parsers.parse('在元素 "{user_name_element}" 中,输入账号 "{username}"'))
def send_username(selenium_tool, user_name_element, username):
    input_element = elements.get_element(selenium_tool.driver, user_name_element)
    input_element.send_keys(username)


@when(parsers.parse('在元素 "{password_element}" 中,输入账号 "{password}"'))
def send_password(selenium_tool, password_element, password):
    input_element = elements.get_element(selenium_tool.driver, password_element)
    input_element.send_keys(password)


@when(parsers.parse('点击 "{button_element}"'))
def click_button(selenium_tool, button_element):
    butt_element = elements.get_element(selenium_tool.driver, button_element)
    butt_element.click()


@when(parsers.parse('校验元素 "{assert_element}" 校验是否包含文本 "{text}"'))
def assert_text(selenium_tool, assert_element, text):
    asserts_element = elements.get_element(selenium_tool.driver, assert_element)
    assert text in asserts_element.text


@then('退出')
def logout(selenium_tool):
    selenium_tool.driver.quit()

接口自动化操作动作

封装接口请求方法:

from pytest_bdd import scenarios, given, when, then, parsers
from pytest_bdd.hooks import *

from common.api_tool import api_test
from common.token_api import get_cms_token, do_logout
from common.assert_tool import assert_tool
from common.yaml_tool import read_yaml
from common.assert_tool import analysis_dict
import ast
import os
import logging


test_result_type_list ={
    '相等校验':0,
    '包含校验':1,
    '字段类型校验':2,
    '字段值校验':3,
    '状态码校验':4
}


class ApiTest:
    def__init__(self):
        self.api =None
        self.methods ='get'
        self.data_type ='params'
        self.params ={}
        self.headers ={}
        self.test_result_type =None
        self.result_text =None
        self.result_type =None
        self.result_data_type =None
        self.result_key =None
        self.file =None
        self.is_logout =False

    def call_api(self, test_body):
        response = api_test(test_body)
    return response

@pytest.fixture
@given('初始化')
def api_tool():
    return ApiTest()

@when(parsers.parse('调用 "{url}" 接口'))
def api(api_tool, url):
    api_tool.api = url


@when(parsers.parse('使用 "{methods}" 请求'))
def methods(api_tool, methods):
    api_tool.methods = methods


@when(parsers.parse('参数类型 "{data_type}"'))
def data_type(api_tool, data_type):
    api_tool.data_type = data_type


@when(parsers.parse('请求头 "{headers}"'))
def headers(api_tool, headers):
    headers_dict = ast.literal_eval(headers)
    for key, value in headers_dict.items():
        api_tool.headers[key]= value


@when(parsers.parse('参数 "{params}"'))
def params(api_tool, params):
    api_tool.params = ast.literal_eval(params)


@when(parsers.parse('校验类型 "{test_result_type}"'))
def result_test(api_tool, test_result_type):
    api_tool.test_result_type = test_result_type_list[test_result_type]


@when(parsers.parse('校验文本 "{result_text}"'))
def result_text(api_tool, result_text):
    api_tool.result_text = result_text


@when(parsers.parse('校验字段类型为 "{result_type}"'))
def result_type(api_tool, result_type):
    api_tool.result_type = result_type

@when(parsers.parse('校验字段 "{result_key}"'))
def result_key(api_tool, result_key):
    api_tool.result_key = result_key

@then('调用成功')
def asserts(api_tool):
    test_data ={'test_result':{'text': api_tool.result_text,'type': api_tool.test_result_type,'result_type': api_tool.result_type,'key': api_tool.result_key}}
    test_body ={'URL': api_tool.api,'method': api_tool.methods,'data_type': api_tool.data_type,'headers': api_tool.headers,'params': api_tool.params,'file': api_tool.file}
    response = api_tool.call_api(test_body)
    logging.info('接口返回:'+str(response.text))
    if api_tool.is_logout:
        do_logout(api_tool.headers['ticket'])
    if api_tool.test_result_type isnotNone:
        assert_tool(response, test_data)

在上述代码中,只展示了python实现测试用例步骤的方式,get_element 、api_tool 等实现方式,会在后面的文章中特意介绍一些工具类的实现。

测试报告生成

pytest-bdd支持运行时指定生成测试报告,命令如下:

pytest --html=report.html

在上述代码中,我们创建了一个测试套件suite,将两个测试用例添加到套件中。然后,使用HTMLTestRunnerHTMLTestRunner类创建一个测试运行器runner,并指定生成的测试报告文件名、标题和描述。最后,通过调用runner.run(suite)执行测试并生成测试报告。

扩展与优化

数据驱动测试

为了使测试更具通用性和可维护性,可以采用数据驱动测试的方法。将测试数据从测试代码中分离出来,存储在CSV、Excel或数据库中。在测试用例执行时,读取不同的测试数据进行测试。例如,可以创建一个数据读取函数,如下所示:

import csv

def read_test_data(file_path):
    test_data =[]
    with open(file_path,'r') as csvfile:
        reader = csv.DictReader(csvfile)
        for row in reader:
            test_data.append(row)
    return test_data

然后,在测试用例中使用读取的测试数据进行测试:

def test_valid_login(self):
    test_data = read_test_data("test_data.csv")
    for data in test_data:
        username = data['username']
        password = data['password']

        # 输入用户名和密码
        self.login_page_elements.username_input().send_keys(username)
        self.login_page_elements.password_input().send_keys(password)

        # 点击登录按钮
        self.login_page_elements.login_button().click()

        # 验证登录成功后的页面标题或其他标识
        expected_title ="Welcome to the Dashboard"
        actual_title = self.driver.title
        self.assertEqual(actual_title, expected_title)

异常处理

在自动化测试过程中,可能会遇到各种异常情况,如页面加载超时、元素找不到等。为了提高工具的稳定性和可靠性,需要对这些异常进行适当的处理。可以使用Selenium的WebDriverWaitexpected_conditions来等待元素出现,并设置超时时间。例如:

from selenium.webdriver.support.ui importWebDriverWait
from selenium.webdriver.support import expected_conditions as EC

def wait_for_element(self, locator, timeout=10):
    return WebDriverWait(self.driver, timeout).until(EC.presence_of_element_located(locator))

# 在页面元素封装类中使用
class LoginPageElements:
    def__init__(self, driver):
        self.driver = driver

    # 用户名输入框
    def username_input(self):
        locator = (By.ID,"username")
        return self.wait_for_element(locator)

    #... 其他元素方法类似

通过这种方式,当元素在指定时间内未出现时,会抛出超时异常,我们可以在测试用例中捕获并处理这个异常,例如记录错误信息或进行重试操作。

日志记录

为了方便调试和跟踪测试过程中的问题,添加日志记录功能是很有必要的。可以使用Python的logging模块来记录测试执行过程中的关键信息、错误和警告等。例如:

import logging

logging.basicConfig(level=logging.INFO,format='%(asctime)s - %(levelname)s - %(message)s')

def test_valid_login(self):
    logging.info("Starting valid login test")
    #... 测试代码
    logging.info("Valid login test completed successfully")

def test_invalid_login(self):
    logging.info("Starting invalid login test")
    #... 测试代码
    logging.error("Invalid login test failed with error: %s", error_message)  # 记录错误信息

通过配置不同的日志级别和格式,可以根据需要灵活地记录测试过程中的信息,有助于快速定位和解决问题。

总结

通过以上步骤,我们成功地开发了一个简单的自动化测试工具。当然,这只是一个基础的示例,你可以根据项目的实际需求和复杂程度进行进一步的扩展和优化。在开发过程中,不断学习和掌握新的技术和方法,如持续集成、分布式测试等,将有助于提高自动化测试工具的效率和质量,为软件项目的顺利交付提供有力保障。希望本文能为你在自动化测试工具开发的道路上提供一些帮助和启示,让你能够更加高效地进行软件测试工作。

后续我们将继续丰富这个工具的内容。关于文中提到的一些封装,后续会继续更新。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值