(二)pytest测试框架

安装

pip install pytest
将浏览器设置为pytest运行:Pycharm-Preferences-Tools-Python intergrated Tools,
将Testing下Default test runner设为pytest

用例识别与运行:

  • 文件范围
    • test_*.py
    • *_test.py
  • 用例识别:
    • Test类包含的所有test_方法
    • 不在class中的所有test_*方法
    • 类中不能用初始化方法

用例控制顺序:

  • setup_module
  • @class_method
  • setup_method
  • setup_function
  • teardown_*
  • pytest-ordering:控制用例之间的顺序
import logging
logging.basicConfig(level=logging.DEBUG)


def setup_module():
    logging.info("setupmodule")


def teardown_module():
    logging.info("teardownmodule")


class TestPytestObject2:
    def test_three(self):
        assert [1, 2] == [1, 3]

    def test_four(self):
        assert {"a": 1, "b": "ss"} == {"a": 2, "b": "ss"}


class TestPytestObject:

    @classmethod
    def setup_class(cls):
        logging.info("setupclass")

    def setup_method(self):
        logging.info("setupmethod")

    def test_two(self):
        assert 1 == 1

    def test_one(self):
        assert True == True

    def teardown_method(self):
        logging.info("teardownmethod")

    @classmethod
    def teardown_class(cls):
        logging.info("teardownclass")

输出:

INFO:root:setupmodule
..                                               [100%]

=========================== 4 passed in 0.07 seconds ===========================

Process finished with exit code 0
INFO:root:setupclass
INFO:root:setupmethod
.INFO:root:teardownmethod
INFO:root:setupmethod
.INFO:root:teardownmethod
INFO:root:teardownclass
INFO:root:teardownmodule

pytest-ordering:控制用例之间的顺序

pip install pytest-ordering

实例应用:

class TestPytestObject:

    @classmethod
    def setup_class(cls):
        logging.info("setupclass")

    def setup_method(self):
        logging.info("setupmethod")

    @pytest.mark.run(order=2)
    def test_two(self):
        assert 1 == 1

    @pytest.mark.run(order=1)
    def test_one(self):
        assert True == True

    def teardown_method(self):
        logging.info("teardownmethod")

    @classmethod
    def teardown_class(cls):
        logging.info("teardownclass")
输出

pytest框架命令:

pytest --collect-only   # 表示把待执行的用例全部展示出来,信息较多
pytest --collect-only -q  # 获取测试名称列表
pytest --collect-only -qq  # 获取测试文件列表及用例数量

参数化

官方:

import pytest

# "test_input,excepted":两个参数
@pytest.mark.parametrize("test_input,excepted", [("3+5", 8), ("2+4", 6), ("6*9", 42)])
def test_eval(test_input, excepted):
    assert eval(test_input) == excepted

执行结果:


image.png
import pytest
from src.calc import Calc

class TestCalc:
    def setup(self) -> None:
        self.calc = Calc()

    @pytest.mark.parametrize("a, b, c", [
        (1, 1, 2),
        (1, 0, 1),
        (1, -1, 0),
        (1, 10000000, 10000001)
    ])
    def test_add(self, a, b, c):
        print(a, b, c)
        assert self.calc.add(a, b) == c

数据驱动

文件参数:json文件

# calc.json文件内容
[
  [2, 1, 3],
  [2, 1, 2],
  [2, 3, 6],
  [3, 1, 3]
]
    @pytest.mark.parametrize("a, b, c", json.load(open("calc.json")))
    def test_div(self, a, b, c):
        assert self.calc.div(a, b) == c

文件参数:yaml文件

# calc.yaml文件内容
- [2, 1, 2]
- [1, 2, 0.5]
- [100, 100, 1]
- [2, 0, null]
 @pytest.mark.parametrize("a, b, c", yaml.load(open("calc2.yaml")))
    def test_div2(self, a, b, c):
        assert self.calc.div(a, b) == c

装饰器

  • 将当前方法以参数形式传递给装饰器
  • 增加函数的功能,又不希望修改函数的定义,这种代码运行期间动态增加功能的方法,叫“装饰器”(Decorator)
  • 装饰器是一个返回函数的高阶函数
def log(func):
      def wrapper(*args, **kwargs):
            print("call %s():" % func.__name__)
            return func(*args, **kwargs)
        return wrapper
@log
def now():
      print("2021-8-23")

调用now()函数,不仅会运行now()函数本身,还会在运行now()函数前打印一行日志:

call now():
2021-8-23

相当于执行了语句:

noe = log(now)

由于log()是一个装饰器,返回一个函数,所以原来now()函数仍然存在,只是现在同名的now变量指向了新的函数,于是调用now()将执行新函数,即在log()函数中返回的wrapper()函数
简单例子:

def before(func):
      def new_now():
            print("setup")
            func()
      return new_now

@before
def now():
      print("2021")

now()
.setup
2021

生成junit报告

pytest --junitxml=[path] [测试用例]
python3 -m pytest --junitxml=/tmp/junitxml /testcase/

bashgems

Fixture参数

方法中有相同的依赖,可通过fixtures参数化

import requests
import pytest


@pytest.fixture()
def topics():
    return requests.get("https://testerhome.com/api/v3/topics.json?limit=2").json()


def test_1(topics):
    assert len(topics["topics"]) == 2


def test_2(topics):
    assert topics["topics"][0]["deleted"] == False

新建网页

cd /tmp/
mkdir www
cd www
wget https://testerhome.com/api/v3/topics.json?limit=2
cp topics.json\?limit\=2 topics.json
python -m http.server

访问生成的地址


页面
import requests
import pytest


@pytest.fixture()
def topics():
    url = "http://0.0.0.0:8000/topics.json"
    # url = "https://testerhome.com/api/v3/topics.json?limit=2"
    return requests.get(url).json()


def test_1(topics):
    assert len(topics["topics"]) == 2


def test_2(topics):
    assert topics["topics"][0]["deleted"] == False
127.0.0.1 - - [08/Sep/2021 14:28:00] "GET /topics.json HTTP/1.1" 200 -
127.0.0.1 - - [08/Sep/2021 14:28:00] "GET /topics.json HTTP/1.1" 200 -

Fixture参数-scope

  • 作用域:控制Fixture的作用范围
  • 默认值是function
  • setup/teardown

setup

@pytest.fixture(scope="module")
def topics():
    url = "http://0.0.0.0:8000/topics.json"
    # url = "https://testerhome.com/api/v3/topics.json?limit=2"
    return requests.get(url).json()


def test_1(topics):
    assert len(topics["topics"]) == 2


def test_2(topics):
    assert topics["topics"][0]["deleted"] == False

结果:只发一次请求,作用于模块

127.0.0.1 - - [08/Sep/2021 14:35:18] "GET /topics.json HTTP/1.1" 200 -
@pytest.fixture(scope="session")
def topics():
    url = "http://0.0.0.0:8000/topics.json"
    # url = "https://testerhome.com/api/v3/topics.json?limit=2"
    return requests.get(url).json()


def test_1(topics):
    assert len(topics["topics"]) == 2


def test_2(topics):
    assert topics["topics"][0]["deleted"] == False

结果:sesion不变只发一次请求

127.0.0.1 - - [08/Sep/2021 14:37:36] "GET /topics.json HTTP/1.1" 200 -

conftest.py

import requests
import pytest


@pytest.fixture(scope="session")
def topics():
    url = "http://0.0.0.0:8000/topics.json"
    # url = "https://testerhome.com/api/v3/topics.json?limit=2"
    return requests.get(url).json()

test_fixtures.py

def test_1(topics):
    assert len(topics["topics"]) == 2


def test_2(topics):
    assert topics["topics"][0]["deleted"] == False

test_fixtures2.py

def test_1(topics):
    assert len(topics["topics"]) == 2


def test_2(topics):
    assert topics["topics"][0]["deleted"] == False
  • scope="module"时,请求2次,两个模块
  • scope="session"时,请求1次,只生成1个session,每次会话只需要运行一次,会话内所有方法及类,模块都共享这个方法
  • 默认时,请求4次,4个方法

teardown

  • yield:当使用yield方法时,在执行过程中,会被对应的方法调用,将调用的方法作为参数传递到引用它的地方(yield后面)
    conftest.py
import requests
import pytest
import logging


logging.basicConfig(level=logging.DEBUG)


@pytest.fixture()
def topics():
    url = "http://0.0.0.0:8000/topics.json"
    logging.info(url)
    # url = "https://testerhome.com/api/v3/topics.json?limit=2"
    yield requests.get(url).json()
    logging.info("after yield")

test_fixtures.py

import logging


def test_1(topics):
    logging.info("start")
    assert len(topics["topics"]) == 2
    logging.info("end")

结果:

test_fixtures.py INFO:root:http://0.0.0.0:8000/topics.json
DEBUG:urllib3.connectionpool:Starting new HTTP connection (1): 0.0.0.0:8000
DEBUG:urllib3.connectionpool:http://0.0.0.0:8000 "GET /topics.json HTTP/1.1" 200 1463
.INFO:root:start
INFO:root:end
INFO:root:after yield

conftest.py

@pytest.fixture()
def topics2(request):
    url = "http://0.0.0.0:8000/topics.json"
    logging.info(url)
    # url = "https://testerhome.com/api/v3/topics.json?limit=2"

    def fin():
        logging.info("after yield teardown")
    request.addfinalizer(fin)
    return requests.get(url).json()

test_fixtures.py

import logging


def test_1(topics2):
    logging.info("start")
    assert len(topics2["topics"]) == 2
    logging.info("end")

结果:

INFO:root:http://0.0.0.0:8000/topics.json
DEBUG:urllib3.connectionpool:Starting new HTTP connection (1): 0.0.0.0:8000
DEBUG:urllib3.connectionpool:http://0.0.0.0:8000 "GET /topics.json HTTP/1.1" 200 1463
.INFO:root:start
INFO:root:end
INFO:root:after yield teardown

param

  • 传参
    conftest.py
@pytest.fixture(params=["https://testerhome.com/api/v3/topics.json?limit=2",
                        "http://0.0.0.0:8000/topics.json", "http://0.0.0.0:8000/topics.json"])
def topics3(request):
    url = request.param
    logging.info(url)
    # url = "https://testerhome.com/api/v3/topics.json?limit=2"

    def fin():
        logging.info("after yield teardown")
    request.addfinalizer(fin)
    return requests.get(url).json()

test_fixtures.py

import logging


def test_1(topics2):
    logging.info("start")
    assert len(topics2["topics"]) == 2
    logging.info("end")


def test_2(topics3):
    assert topics3["topics"][0]["deleted"] == False

结果:


结果

分组

  • @pyteset.mark.x
import logging
import pytest


@pytest.mark.a
def test_1(topics2):
    logging.info("start")
    assert len(topics2["topics"]) == 2
    logging.info("end")


@pytest.mark.b
def test_2(topics3):
    assert topics3["topics"][0]["deleted"] == False


@pytest.mark.b
def test_3(topics2):
    assert topics2["topics"][0]["deleted"] == False

执行命令:pytest -m b,只执行b组的用例

(venv) MacBook-Pro-68:testcase emily$ pytest -m b

你可能感兴趣的:((二)pytest测试框架)