关键字驱动模型实战之测试运行

实验介绍

实验内容

在《Python Web 自动化测试入门实战》初级实验中给大家介绍过五种测试模型,其中线性模型、模块化驱动模型和行为驱动模型以实例给大家做了介绍。在本系列第七次和第八次实验给大家介绍了数据驱动模型。接下来会使用 pytest 框架实战关键字驱动模型。

在第十一次和第十二次中,我们分别对数据测试数据和对象操作进行了设计,本次实验我们将两者联系在一起,并且给测试项目添加日志,然后运行生成测试报告。

知识点

  • 添加自动化测试用例
  • 添加日志
  • 日志深入记录
  • 自动化项目运行
  • 添加项目说明

代码获取

你可以通过下面的命令下载本节实验的代码到虚拟环境中,作为参照对比:

# 下载代码
wget https://labfile.oss-internal.aliyuncs.com/courses/3776/130.zip

# 解压缩代码
unzip 130.zip

添加自动化测试用例

本节实验我们来收集测试用例,并且使用 pytest 框架的参数化功能,将测试用例添加到对象执行上。

添加共享 Fixture

通过 VS Code 工具在 /home/shiyanlou/Code/KeywordDrivenModel/case/ 下新建文件 conftest.py ,编写如下内容:

import pytest
from selenium import webdriver


@pytest.fixture(scope='session')
def setup_teardown():
    driver = webdriver.Firefox()

    yield driver
    driver.quit()

我们将浏览器的启动和关闭设置为前置条件和后置条件,作用域设置为 session 级别。整个自动化测试中只启动一次浏览器。

测试用例执行

通过 VS Code 工具在 /home/shiyanlou/Code/KeywordDrivenModel/case/ 下新建文件 test_case.py ,编写如下内容:

# -*-coding:utf-8-*-

import pytest
import os, sys

current_path = os.path.abspath(os.path.dirname(__file__))
autotest_path = os.path.join(current_path, os.path.pardir)
sys.path.append(autotest_path)
from common.excel_util import ExcelUtil
from action.operation import Operation


def case(excel=None, sheet=None):
    return ExcelUtil(excel, sheet).get_case()

@pytest.fixture(params=case(sheet='login'))
def fixture_params(request):
    return request.param


def test_run(setup_teardown, fixture_params):
    driver = setup_teardown

    Operation(driver).operate(fixture_params[0], fixture_params[1], fixture_params[2])


if __name__ == '__main__':
    pytest.main(['-s','case/test_case.py'])

在文件 test_case.py 下我们定义了三个函数,case() 函数是返回测试用例; fixture_params() 是代码在执行时会使用 pytest 内置的固件 request,并通过 request.param 来获取参数; test_run() 是使用我们在 /home/shiyanlou/Code/KeywordDrivenModel/action/operation.py 文件中定义的 Operation() 类中的 operate() 方法执行测试。

然后点击 VS Code 工具右上角的执行按钮执行文件,截图如下:

从结果中可以看到,一共执行了 12 条测试用例,与我们 Excel 表格中 12 条测试数据数量相吻合。

添加日志

日志是对软件执行时所发生事件的一种追踪方式。在本系列实验第九次实验中已经给大家介绍过了日志的使用。在此就直接上代码,不做介绍了。因为代码和第九次实验最后产生的 /home/shiyanlou/test/create_logging_file.py 文件代码相同。

添加创建日志代码

通过 VS Code 工具在 /home/shiyanlou/Code/KeywordDrivenModel/common/ 下新建文件 log_util.py ,编写如下内容:

import logging
import datetime
import os


class LogUtil:
    def __init__(self):
        current_path = os.path.abspath(os.path.dirname(__file__))
        autotest_path = os.path.join(current_path, os.path.pardir)
        file_name = datetime.datetime.now().strftime(autotest_path + "/log/%y-%m-%d-%H:%M") + '.log'
        self.file_formate = logging.Formatter('%(asctime)s - %(filename)s - %(levelname)s - %(message)s')

        self.logger = logging.getLogger(__name__)
        self.logger.setLevel(logging.INFO)
        # 将日志写入磁盘
        self.file_handler = logging.FileHandler(file_name, 'a', encoding='utf-8')
        self.file_handler.setLevel(logging.INFO)
        self.file_handler.setFormatter(self.file_formate)
        self.logger.addHandler(self.file_handler)

    def get_log(self):
        """获取 logger"""
        return self.logger

    def close_handler(self):
        """关闭 handler"""
        self.logger.removeHandler(self.file_handler)
        self.file_handler.close()

    def console_print(self):
        """控制台输出"""
        console = logging.StreamHandler()
        console.setLevel(logging.INFO)
        console.setFormatter(self.file_formate)

我们以运行时间命名日志文件,并且将其保存在 /home/shiyanlou/Code/KeywordDrivenModel/log/ 下。

修改 conftest.py

添加创建日志文件代码完成后,我们需要将日志在项目运行中使用。

修改 /home/shiyanlou/Code/KeywordDrivenModel/case/conftest.py 文件,添加日志写入,并且将 logger 和浏览器 driver 一样以参数的形式传入到测试用例执行中。

修改后的 /home/shiyanlou/Code/KeywordDrivenModel/case/conftest.py 文件内容如下:

import pytest
import os, sys
import logging
from selenium import webdriver

current_path = os.path.abspath(os.path.dirname(__file__))
autotest_path = os.path.join(current_path, os.path.pardir)
sys.path.append(autotest_path)
from common.log_util import LogUtil


@pytest.fixture(scope='session')
def setup_teardown():
    driver = webdriver.Firefox()

    logging_file = LogUtil()
    logger = logging_file.get_log()
    logging_file.console_print()
    logger.info('START ---- browser')

    yield driver, logger
    driver.quit()

    logger.info('END ---- browser')
    logging_file.close_handler()

在浏览器启动成功后添加浏览器启动日志信息,浏览器关闭后添加浏览器运行结束的日志信息。

修改 test_case.py

接下来修改 /home/shiyanlou/Code/KeywordDrivenModel/case/test_case.py 文件中的 test_run() 函数。修改后的内容如下:

def test_run(setup_teardown, fixture_params):
    driver, logger = setup_teardown

    try:
        Operation(driver).operate(fixture_params[0], fixture_params[1], fixture_params[2])
        logger.info("FINISHED ---- {} -- {} -- {}".format(fixture_params[0], fixture_params[1], fixture_params[2]))
    except:
        logger.exception("ERROR ---- {} -- {} -- {}".format(fixture_params[0], fixture_params[1], fixture_params[2]))

我们在对象操作运行结束后添加此条测试数据运行完成的日志信息,如果此条测试数据对象操作运行失败,则添加出错的日志信息。

然后点击 VS Code 工具右上角的执行按钮执行文件 /home/shiyanlou/Code/KeywordDrivenModel/case/test_case.py 。

代码执行结束后会在 /home/shiyanlou/Code/KeywordDrivenModel/log/ 下产生一个以时间命名且是 .log 格式的日志文件。我们来查看此文件内容,截图如下:

日志深入记录

在上小节添加日志中,大家有没有发现在修改 /home/shiyanlou/Code/KeywordDrivenModel/case/test_case.py 文件中 test_run() 函数时我们添加了一个 try...except... 语句,这是一个非常不合理的做法。如果当我们的测试数据在对象操作 Operation(driver).operate(fixture_params[0], fixture_params[1], fixture_params[2]) 中运行失败,那么此时程序会走 except,也就是说我们的测试函数 test_run() 依旧是成功的,在测试结果中展示的永远是成功,如果想要查看失败的测试用例只能在日志中查看,这显然是不明智的。

修改 test_case.py

接下来我们重新修改 /home/shiyanlou/Code/KeywordDrivenModel/case/test_case.py 文件中的 test_run() 函数。修改后的内容如下:

def test_run(setup_teardown, fixture_params):
    driver, logger = setup_teardown

    logger.info("START RUN ---- {} -- {} -- {}".format(fixture_params[0], fixture_params[1], fixture_params[2]))

    Operation(driver, logger).operate(fixture_params[0], fixture_params[1], fixture_params[2])

    logger.info("FINISH ---- {} -- {} -- {}".format(fixture_params[0], fixture_params[1], fixture_params[2]))

我们在对象操作 Operation(driver, logger).operate(fixture_params[0], fixture_params[1], fixture_params[2]) 前添加开始执行的日志信息,运行结束后添加执行完成的日志信息。并且将 logger 以参数的形式传入 Operation() 类。

修改 operation.py

接着修改 /home/shiyanlou/Code/KeywordDrivenModel/action/operation.py 文件。

logger 传入后就需要接收,我们先来接收,然后将其继续传入 Browser() 类、 TimeSet() 类、 Element() 类。

在此只做 Element() 类的传入,其他的类似,大家可自行照此深入写入。

修改后的 /home/shiyanlou/Code/KeywordDrivenModel/action/operation.py 文件内容如下:
import os, sys

current_path = os.path.abspath(os.path.dirname(__file__))
autotest_path = os.path.join(current_path, os.path.pardir)
sys.path.append(autotest_path)

from action.browser import Browser
from action.time_set import TimeSet
from action.element import Element


class Operation:
    def __init__(self, driver, logger):
        self.driver = driver
        self.logger = logger

    def operate(self, obj, action, parameter=None):
        """
        对象操作
        :param obj:
        :param action:
        :param parameter:
        :return:
        """
        obj = obj.lower()
        if obj in ["browser", "driver", "webdriver"]:
            return Browser(self.driver).browser_operate(action, parameter)
        elif obj == "time":
            return TimeSet().time_operate(action, parameter)
        elif obj is None&nbs***bsp;obj == "":
            self.logger.warning("obj keyword is none&nbs***bsp;empty ---- {} -- {} -- {}".format(obj, action, parameter))
            return
        # 如果不是其他的关键字对象,则就认为是元素对象
        else:
            return Element(self.driver, self.logger).element_operate(obj, action, parameter)

修改 element.py

我们接着修改 /home/shiyanlou/Code/KeywordDrivenModel/action/element.py 文件。

首先修改 Element() 类下的构造函数 __init__() 。修改后的代码如下:

    def __init__(self, driver, logger):
        self.driver = driver
        self.logger = logger
然后修改 element_operate() 方法,修改后的代码如下:
   def element_operate(self, local, action, parameter=None):
        """
        元素操作
        :param local:
        :param action:
        :param parameter:
        :return:
        """
        self.logger.info("element action start ---- {} -- {} -- {}".format(local, action, parameter))

        element = self.__local_element(local)
        if element:
            self.logger.info("element local finished ---- {} -- {} -- {}".format(local, action, parameter))
        else:
            self.logger.exception("element local fail ---- {} -- {} -- {}".format(local, action, parameter))

        if action == "input" and parameter:
            element.send_keys(parameter)
            self.logger.info("element input finished ---- {} -- {} -- {}".format(local, action, parameter))
            return
        elif action == "click":
            element.click()
            self.logger.info("element click finished ---- {} -- {} -- {}".format(local, action, parameter))
            return
        elif action == "assert":
            AssertText(self.driver, element, parameter).equal()
            self.logger.info("element assert finished ---- {} -- {} -- {}".format(local, action, parameter))
            return
        else:
            self.logger.exception("element action fail ---- {} -- {} -- {}".format(local, action, parameter))
            return

给每一步都添加日志记录信息。

下面点击 VS Code 工具右上角的执行按钮,执行文件 /home/shiyanlou/Code/KeywordDrivenModel/case/test_case.py 。

代码执行结束后会在 /home/shiyanlou/Code/KeywordDrivenModel/log/ 下产生一个新的日志文件。我们来查看此文件内容,截图如下:

从日志信息中可以,记录的内容更丰富,更详细。在后期的问题定位中会更容易。

自动化项目运行

下面我们来编辑 /home/shiyanlou/Code/KeywordDrivenModel/run.py 文件,内容与第八次实验数据驱动模型中的执行文件 /home/shiyanlou/Code/DataDrivenModel/run.py 内容类似,就不做解释,直接写代码。

编辑 /home/shiyanlou/Code/KeywordDrivenModel/run.py 文件内容如下:

# -*- coding: utf-8 -*-

"""
Created on 2021-4-20
Project: KeywordDrivenModel
@Author: Tynam
"""

import os
import pytest


current_path = os.path.abspath(os.path.dirname(__file__))
case_path = os.path.join(current_path, 'case')

def run_case():
    pytest.main(['-v', case_path])

def create_report():
    run_case = """
        cd %s &
        pytest %s --alluredir ./report/result/
        """ % (current_path, case_path)

    generate_report = """
        cd %s &
        allure generate ./report/result/ -o ./report/report/ --clean
        """ % (current_path)

    os.system(run_case)
    os.system(generate_report)


if __name__ == '__main__':
    # run_case()
    create_report()

然后点击 VS Code 工具右上角的执行按钮执行文件,截图如下:

我们在 VS Code 工具中使用 Live Server 打开 /home/shiyanlou/Code/DataDrivenModel/report/report/index.html。截图如下:

至此,我们关键字驱动模型实验已经完成。

添加项目说明

编辑 /home/shiyanlou/Code/KeywordDrivenModel/readme.md 文件,添加项目说明,编写规则。

内容如下:

## 目录结构

- action:对象执行目录,包括浏览器操作、元素操作等。

- case:测试用例操作目录。

- common:公共层,存放数据文件的读写、日志写入等公共文件。

- data:数据目录,存放使用 Excel 写入的测试数据。

- log:日志目录,测试执行后产生的日志文件保存在此目录。

- report:存放测试报告。

- `run.py`:运行脚本并生成测试报告。

## Excel 编写规则

####浏览器操作

| 描述     | 对象   | 操作 | 参数 |
| ------------ | ------- | ----- | ---- |
| 最大化    | browser | max  |    |
| 访问网页   | browser | open | url  |
| 关闭当前 tab | browser | close |     |
| 结束进程   | browser | quit |    |

### 时间操作

| 描述   | 对象 | 操作  | 参数         |
| -------- | ---- | ----- | -------------------- |
| 时间等待 | time | sleep | 等待时间值(单位秒) |

### 元素操作

| 描述           | 对象      | 操作   | 参数     | 备注                                                          |
| ------------------------ | ------------- | ------   | ------------ | ------------------------------------------------------------- |
| 元素定位         | 定位字符串   |      |         | 默认 CSS 定位                                                 |
| 其他方式定位       | by:定位字符串 |      |        | by 是定位方式,可选值:css、id、class、name、tag、link、xpath |
| 输入框输入内容      | 定位字符串   | input   | 输入的字符串 |                                                               |
| 点击           | 定位字符串   | click   |        |                                                               |
| 断言                      | 定位字符串| assert       | 预期字符串    | 只支持文本内容断言                                            |
| Alert 弹窗文本字符串断言 | alert      | assert   | 预期字符串  |                                                               |

写入后在 VS Code 中预览,截图如下所示:

实验总结

实验知识点回顾:

  • 添加自动化测试用例
  • 添加日志
  • 日志深入记录
  • 自动化项目运行
  • 添加项目说明

本次实验是对关键字驱动模型的实战练习。大家在以后的学习、工作中,需要灵活运用。思路,解决方案都是类似的,面对不同的项目测试、不同的测试场景都可以变通无碍。