单元测试
基础知识
单元测试概念
unittest 模块
- 测试文件以
test_开头
- 测试文件中的类以
Test开头
- 类中的测试方法以
test_开头
- 测试类需要继承
unittest.TestCase
创建目录结构

1 2 3 4 5 6
| class Calculator: def add(self, *args): result = 0 for arg in args: result += arg return result
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import unittest from myProject.basic_knowledge.calculator import Calculator
class TestCalculator(unittest.TestCase): def test_add(self): cal = Calculator() excepted_result = 10
actual_result = cal.add(5, 5)
self.assertEqual(excepted_result, actual_result)
|

批量测试
- 安装
nose模块和coverage模块
- 运行方法
- 运行单个测试文件
python3 -m unittest -v myTest.basic_knowledge.test_calculator
- 运行所有测试文件
nosetests -v myTest/*
- 统计测试覆盖率
nosetests --with-coverage --cover-erase -v myTest/*
断言 assert
assertEqual()
assertTrue()
assertFalse()
assertRaises() 支持上下文管理器
1 2 3 4 5 6 7 8
| class Downloads: def get_downloads(self, url: str): if url and url == "https://www.baidu.com": return True elif url: return False else: raise Exception("url is empty")
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import unittest from myProject.assert_func.downloads import Downloads
class TestDownloads(unittest.TestCase): def test_get_downloads_true(self): down = Downloads() url = "https://www.baidu.com" excepted_result = True
actual_result = down.get_downloads(url)
self.assertEqual(excepted_result, actual_result)
def test_get_downloads_false(self): down = Downloads() url = "www.google.com" excepted_result = False
actual_result = down.get_downloads(url)
self.assertEqual(excepted_result, actual_result)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| (venv) ➜ pythonUnitTest nosetests --with-coverage --cover-erase -v myTest/* test_get_downloads_false (myTest.assert_func.test_downloads.TestDownloads) ... ok test_get_downloads_true (myTest.assert_func.test_downloads.TestDownloads) ... ok test_add (myTest.basic_knowledge.test_calculator.TestCalculator) ... ok test_add2 (myTest.basic_knowledge.test_calculator.TestCalculator) ... ok
Name Stmts Miss Cover ------------------------------------------------------------- myProject/__init__.py 0 0 100% myProject/assert_func/__init__.py 0 0 100% myProject/assert_func/downloads.py 7 1 86% myProject/basic_knowledge/__init__.py 0 0 100% myProject/basic_knowledge/calculator.py 6 0 100% myTest/__init__.py 0 0 100% myTest/assert_func/__init__.py 0 0 100% myTest/basic_knowledge/__init__.py 0 0 100% ------------------------------------------------------------- TOTAL 13 1 92% ---------------------------------------------------------------------- Ran 4 tests in 0.002s
OK
|
修改了 test_downloads.py 后
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| import unittest from myProject.assert_func.downloads import Downloads
class TestDownloads(unittest.TestCase): def test_get_downloads_true(self): down = Downloads() url = "https://www.baidu.com" excepted_result = True
actual_result = down.get_downloads(url)
self.assertEqual(excepted_result, actual_result)
def test_get_downloads_false(self): down = Downloads() url = "www.google.com"
actual_result = down.get_downloads(url)
self.assertFalse(actual_result)
def test_get_downloads_exception(self): down = Downloads() url = ""
with self.assertRaises(Exception): down.get_downloads(url)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| (venv) ➜ pythonUnitTest nosetests --with-coverage --cover-erase -v myTest/* test_get_downloads_exception (myTest.assert_func.test_downloads.TestDownloads) ... ok test_get_downloads_false (myTest.assert_func.test_downloads.TestDownloads) ... ok test_get_downloads_true (myTest.assert_func.test_downloads.TestDownloads) ... ok test_add (myTest.basic_knowledge.test_calculator.TestCalculator) ... ok test_add2 (myTest.basic_knowledge.test_calculator.TestCalculator) ... ok
Name Stmts Miss Cover ------------------------------------------------------------- myProject/__init__.py 0 0 100% myProject/assert_func/__init__.py 0 0 100% myProject/assert_func/downloads.py 7 0 100% myProject/basic_knowledge/__init__.py 0 0 100% myProject/basic_knowledge/calculator.py 6 0 100% myTest/__init__.py 0 0 100% myTest/assert_func/__init__.py 0 0 100% myTest/basic_knowledge/__init__.py 0 0 100% ------------------------------------------------------------- TOTAL 13 0 100% ---------------------------------------------------------------------- Ran 5 tests in 0.002s
OK
|
Test Fixtures
在执行测试方法之前或者之后的内容称为 Test Fixtures
比如说需要给每个测试方法写一个数据库连接,断开 会很麻烦和造成较大开销
模块级别的 Fixtures
setUpModule()
tearDownModule()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| class BankAccount:
def __init__(self, balance: float): self.balance = balance
@property def balance(self): return self.__balance
@balance.setter def balance(self, value: float): if value < 0: raise ValueError("Balance cannot be negative") self.__balance = value
def deposit(self, amount: float): if amount <= 0: raise ValueError("Amount must be positive") self.balance += amount
def withdraw(self, amount: float): if amount <= 0: raise ValueError("Amount must be positive") if amount > self.balance: raise ValueError("Insufficient funds") self.balance -= amount
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import unittest from myProject.fixtures.bank_account import BankAccount
def setUpModule(): print("calling setup module")
def tearDownModule(): print("calling teardown module")
class TestBankAccount(unittest.TestCase): def test_deposit(self): account = BankAccount(100) account.deposit(50) self.assertEqual(150, account.balance)
def test_withdraw(self): account = BankAccount(100) account.withdraw(50) self.assertEqual(50, account.balance)
|
1 2 3 4 5 6 7 8 9 10
| (venv) ➜ pythonUnitTest python3 -m unittest -v myTest.fixtures.test_bank_account calling setup module test_deposit (myTest.fixtures.test_bank_account.TestBankAccount) ... ok test_withdraw (myTest.fixtures.test_bank_account.TestBankAccount) ... ok calling teardown module
---------------------------------------------------------------------- Ran 2 tests in 0.000s
OK
|
类级别的 Fixtures
setUpClass(cls)
tearDownClass(cls)
1 2 3 4 5 6 7
| @classmethod def setUpClass(cls) -> None: print("calling setup class")
@classmethod def tearDownClass(cls) -> None: print("calling teardown class")
|
1 2 3 4 5 6 7 8 9 10 11 12
| (venv) ➜ pythonUnitTest python3 -m unittest -v myTest.fixtures.test_bank_account calling setup module calling setup class test_deposit (myTest.fixtures.test_bank_account.TestBankAccount) ... ok test_withdraw (myTest.fixtures.test_bank_account.TestBankAccount) ... ok calling teardown class calling teardown module
---------------------------------------------------------------------- Ran 2 tests in 0.000s
OK
|
方法级别的 Fixtures
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| import unittest from myProject.fixtures.bank_account import BankAccount
def setUpModule(): print("calling setup module")
def tearDownModule(): print("calling teardown module")
class TestBankAccount(unittest.TestCase): @classmethod def setUpClass(cls) -> None: print("calling setup class")
@classmethod def tearDownClass(cls) -> None: print("calling teardown class")
def setUp(self) -> None: self.account = BankAccount(100) print("calling setup")
def tearDown(self) -> None: self.account = None print("calling teardown")
def test_deposit(self): self.account.deposit(50) self.assertEqual(150, self.account.balance)
def test_withdraw(self): self.account.withdraw(50) self.assertEqual(50, self.account.balance)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| (venv) ➜ pythonUnitTest python3 -m unittest -v myTest.fixtures.test_bank_account calling setup module calling setup class test_deposit (myTest.fixtures.test_bank_account.TestBankAccount) ... calling setup calling teardown ok test_withdraw (myTest.fixtures.test_bank_account.TestBankAccount) ... calling setup calling teardown ok calling teardown class calling teardown module
---------------------------------------------------------------------- Ran 2 tests in 0.000s
OK
|
Mock
mock 即模拟函数、方法、类的功能,在运行测试代码的时候,有哪些不是真的想要调用的代码块或方法,可以使用 mock 进行模拟调用
unittest.mock模块提供了Mock 和MagicMock两个类
Mock用于模拟指定的方法或属性
MagicMock是 Mock 的子类,用于模拟Magic方法
1 2 3 4
| class Student: def __init__(self, id, name): self.id = id self.name = name
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| from myProject.mock_test.Student import Student
def find_by_stu_id(stuid: int) -> Student: pass
def save_stu(stu: Student): pass
def s(stid: int, stuname: str): stu = find_by_stu_id(stid) if stu is not None: stu.name = stuname save_stu(stu)
|
现在要测试上面alter_stu_name方法中的代码逻辑,但不调用其他方法包括:find_by_stu_id、
save_stu
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| import unittest from unittest.mock import Mock
from myProject.mock_test import student_service
class TestStudentService(unittest.TestCase): def test_alter_stu_name_with_student(self): student_service.find_by_stu_id = Mock() student = Mock(id=1, name="Zachary") student_service.find_by_stu_id.return_value = student student_service.save_stu = Mock()
student_service.alter_stu_name(1, "Zach")
self.assertEqual("Zach", student.name) student_service.save_stu.assert_called()
def test_alter_stu_name_without_student(self): student_service.find_by_stu_id = Mock() student_service.find_by_stu_id.return_value = None student_service.save_stu = Mock()
student_service.alter_stu_name(1, "Zach")
student_service.save_stu.assert_not_called()
|
patch
帮助我们使用 Mock 替换测试代码块中的某些方法、类的调用
- patch 可以替换的目标
- 目标必须是可以 import 的
- 是在调用的地方替换,原先的定义不进行替换
patch 装饰器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import unittest from unittest.mock import patch, Mock
from myProject.mock_test import student_service
class TestStudentService(unittest.TestCase):
@patch("myProject.mock_test.student_service.save_stu") @patch("myProject.mock_test.student_service.find_by_stu_id") def test_alter_stu_name_with_student_decorator(self, mock_find_by_stu_id, mock_save_stu): student = Mock(id=1, name="Zachary") mock_find_by_stu_id.return_value = student
student_service.alter_stu_name(1, "Zach")
self.assertEqual("Zach", student.name) mock_save_stu.assert_called()
|
上下文管理器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import unittest from unittest.mock import patch, Mock
from myProject.mock_test import student_service
class TestStudentService(unittest.TestCase): @patch("myProject.mock_test.student_service.find_by_stu_id") def test_alter_stu_name_with_student_context_manager(self, mock_find_by_stu_id): student = Mock(id=1, name="Zachary") mock_find_by_stu_id.return_value = student
with patch("myProject.mock_test.student_service.save_stu") as mock_save_stu: student_service.alter_stu_name(1, "Zach")
self.assertEqual("Zach", student.name) mock_save_stu.assert_called()
|
手动调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import unittest from unittest.mock import patch, Mock
from myProject.mock_test import student_service
class TestStudentService(unittest.TestCase):
@patch("myProject.mock_test.student_service.find_by_stu_id") def test_alter_stu_name_with_student_manual(self, mock_find_by_stu_id): student = Mock(id=1, name="Zachary") mock_find_by_stu_id.return_value = student
patcher = patch("myProject.mock_test.student_service.save_stu") patcher.start() student_service.alter_stu_name(1, "Zach")
self.assertEqual("Zach", student.name) patcher.stop()
|
测试实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import os.path from urllib.request import Request, urlopen
class ProductService: def download_img(self, url: str): site_url = Request(url, headers={'User-Agent': 'Mozilla/5.0'}) with urlopen(site_url) as response: image_date = response.read()
if not image_date: raise Exception("Error: No image data from url: " + url)
filename = os.path.basename(url) with open(filename, 'wb') as f: f.write(image_date)
return f"Downloaded {filename}"
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| import unittest from unittest.mock import patch, MagicMock
from myProject.test_example.product_service import ProductService
class TestProductService(unittest.TestCase): def setUp(self) -> None: self.service = ProductService()
def tearDown(self) -> None: self.service = None
@patch('myProject.test_example.product_service.urlopen') @patch('myProject.test_example.product_service.Request.__new__') def test_download_img_with_exception(self, mock_request, mock_urlopen): url = "https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png" return_mock_urlopen = MagicMock() response_mock = MagicMock() mock_urlopen.return_value = return_mock_urlopen return_mock_urlopen.__enter__.return_value = response_mock response_mock.read.return_value = None
with self.assertRaises(Exception): self.service.download_img(url)
@patch('builtins.open') @patch('os.path.basename') @patch('myProject.test_example.product_service.urlopen') @patch('myProject.test_example.product_service.Request.__new__') def test_download_img_with_success(self, mock_request, mock_urlopen, mock_basename, mock_open): url = "https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png" return_mock_urlopen = MagicMock() response_mock = MagicMock() mock_urlopen.return_value = return_mock_urlopen return_mock_urlopen.__enter__.return_value = response_mock response_mock.read.return_value = "value" mock_basename.return_value = "filename" excepted_result = f"Downloaded {mock_basename.return_value}"
result = self.service.download_img(url)
self.assertEqual(excepted_result, result)
|
测试覆盖率
统计的是在单元测试中有多少代码行被执行了
覆盖率 = 执行的代码行/总代码行
- 统计测试覆盖率
python -m coverage run -m unittest
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| (venv) ➜ pythonUnitTest python -m coverage run -m unittest .....calling setup module calling setup class calling setup calling teardown .calling setup calling teardown .calling teardown class calling teardown module ....... ---------------------------------------------------------------------- Ran 14 tests in 0.009s
OK
|
- 查看覆盖率报告
python -m coverage report
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| (venv) ➜ pythonUnitTest python -m coverage report Name Stmts Miss Cover ----------------------------------------------------------------- myProject/__init__.py 0 0 100% myProject/assert_func/__init__.py 0 0 100% myProject/assert_func/downloads.py 7 0 100% myProject/basic_knowledge/__init__.py 0 0 100% myProject/basic_knowledge/calculator.py 6 0 100% myProject/fixtures/__init__.py 0 0 100% myProject/fixtures/bank_account.py 20 4 80% myProject/mock_test/Student.py 4 2 50% myProject/mock_test/__init__.py 0 0 100% myProject/mock_test/student_service.py 10 2 80% myProject/test_example/__init__.py 0 0 100% myProject/test_example/product_service.py 13 0 100% myTest/__init__.py 0 0 100% myTest/assert_func/__init__.py 0 0 100% myTest/assert_func/test_downloads.py 19 0 100% myTest/basic_knowledge/__init__.py 0 0 100% myTest/basic_knowledge/test_calculator.py 13 0 100% myTest/fixtures/__init__.py 0 0 100% myTest/fixtures/test_bank_account.py 25 0 100% myTest/mock_test/__init__.py 0 0 100% myTest/mock_test/test_student_service.py 18 0 100% myTest/patch_test/__init__.py 0 0 100% myTest/patch_test/test_student_service.py 26 0 100% myTest/test_example/__init__.py 0 0 100% myTest/test_example/test_product_service.py 32 0 100% ----------------------------------------------------------------- TOTAL 193 8 96%
|
- 生成 web 格式的报告
python -m coverage html
1 2
| (venv) ➜ pythonUnitTest python -m coverage html Wrote HTML report to htmlcov/index.html
|


可以点击查看具体哪里没有测试到

PyTest
基于 Python 的第三方测试框架
安装
pip install pytest
运行
- 在项目路径下使用
pytest命令
- 自动查找所有
test_*.py或者*_test.py的测试文件
- 自动查找所有
test_开头的文件,Test开头的类中test_开头的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| (venv) ➜ pythonUnitTest pytest ====================================================== test session starts ======================================================= platform darwin -- Python 3.7.7, pytest-7.4.4, pluggy-1.2.0 rootdir: /Users/zachary/Documents/PythonCode/pythonUnitTest collected 14 items
myTest/assert_func/test_downloads.py ... [ 21%] myTest/basic_knowledge/test_calculator.py .. [ 35%] myTest/fixtures/test_bank_account.py .. [ 50%] myTest/mock_test/test_student_service.py .. [ 64%] myTest/patch_test/test_student_service.py ... [ 85%] myTest/test_example/test_product_service.py .. [100%]
======================================================= 14 passed in 0.14s =======================================================
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| (venv) ➜ pythonUnitTest pytest -v ====================================================== test session starts ======================================================= platform darwin -- Python 3.7.7, pytest-7.4.4, pluggy-1.2.0 -- /Users/zachary/Documents/PythonCode/pythonUnitTest/venv/bin/python cachedir: .pytest_cache rootdir: /Users/zachary/Documents/PythonCode/pythonUnitTest collected 14 items
myTest/assert_func/test_downloads.py::TestDownloads::test_get_downloads_exception PASSED [ 7%] myTest/assert_func/test_downloads.py::TestDownloads::test_get_downloads_false PASSED [ 14%] myTest/assert_func/test_downloads.py::TestDownloads::test_get_downloads_true PASSED [ 21%] myTest/basic_knowledge/test_calculator.py::TestCalculator::test_add PASSED [ 28%] myTest/basic_knowledge/test_calculator.py::TestCalculator::test_add2 PASSED [ 35%] myTest/fixtures/test_bank_account.py::TestBankAccount::test_deposit PASSED [ 42%] myTest/fixtures/test_bank_account.py::TestBankAccount::test_withdraw PASSED [ 50%] myTest/mock_test/test_student_service.py::TestStudentService::test_alter_stu_name_with_student PASSED [ 57%] myTest/mock_test/test_student_service.py::TestStudentService::test_alter_stu_name_without_student PASSED [ 64%] myTest/patch_test/test_student_service.py::TestStudentService::test_alter_stu_name_with_student_context_manager PASSED [ 71%] myTest/patch_test/test_student_service.py::TestStudentService::test_alter_stu_name_with_student_decorator PASSED [ 78%] myTest/patch_test/test_student_service.py::TestStudentService::test_alter_stu_name_with_student_manual PASSED [ 85%] myTest/test_example/test_product_service.py::TestProductService::test_download_img_with_exception PASSED [ 92%] myTest/test_example/test_product_service.py::TestProductService::test_download_img_with_success PASSED [100%]
======================================================= 14 passed in 0.14s =======================================================
|
- 使用
pytest 指定文件路径 对单独某个文件进行测试
1 2 3 4 5 6 7 8 9 10 11
| (venv) ➜ pythonUnitTest pytest -v myTest/test_example/test_product_service.py ====================================================== test session starts ======================================================= platform darwin -- Python 3.7.7, pytest-7.4.4, pluggy-1.2.0 -- /Users/zachary/Documents/PythonCode/pythonUnitTest/venv/bin/python cachedir: .pytest_cache rootdir: /Users/zachary/Documents/PythonCode/pythonUnitTest collected 2 items
myTest/test_example/test_product_service.py::TestProductService::test_download_img_with_exception PASSED [ 50%] myTest/test_example/test_product_service.py::TestProductService::test_download_img_with_success PASSED [100%]
======================================================= 2 passed in 0.05s ========================================================
|
- 使用
pytest -s输出调试信息,比如 print 等的打印信息
- 使用
pytest -x在遇到错误测试的时候 会立即停止
- 跳过指定测试内容
@pytest.mark.skip
@pytest.mark.skipif
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| import sys import unittest
import pytest
from myProject.mock_test.Student import Student
def skip_condition_macos(): return sys.platform.casefold() == 'darwin'.casefold()
def skip_condition_linux(): return sys.platform.casefold() == 'linux'.casefold()
class TestStudent(unittest.TestCase): def setUp(self) -> None: self.student = Student(1, "Zach")
def tearDown(self) -> None: self.student = None
@pytest.mark.skip(reason="testing case expired") def test_alter_stu_name(self): self.student.name = "Zachary" self.assertEqual("Zachary", self.student.name)
@pytest.mark.skipif(condition=skip_condition_macos(), reason="currently platform not supported") def test_alter_stu_id(self): self.student.id = 2 self.assertEqual(2, self.student.id)
|
1 2 3 4 5 6 7 8 9 10 11
| (venv) ➜ pythonUnitTest pytest -v myTest/pytest/test_student.py ====================================================== test session starts ======================================================= platform darwin -- Python 3.7.7, pytest-7.4.4, pluggy-1.2.0 -- /Users/zachary/Documents/PythonCode/pythonUnitTest/venv/bin/python cachedir: .pytest_cache rootdir: /Users/zachary/Documents/PythonCode/pythonUnitTest collected 2 items
myTest/pytest/test_student.py::TestStudent::test_alter_stu_id SKIPPED (currently platform not supported) [ 50%] myTest/pytest/test_student.py::TestStudent::test_alter_stu_name SKIPPED (testing case expired) [100%]
======================================================= 2 skipped in 0.01s =======================================================
|
Fixtures
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Student: def __init__(self, id, name): self.id = id self.name = name
def alter_name(self, name: str) -> bool: if 3 < len(name) < 8: self.name = name return True return False
def is_valid_name(self) -> bool: if self.name: return 3 < len(self.name) < 8 return False
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import pytest
from myProject.pytest_fixture.Student import Student
class TestStudent: @pytest.fixture def valid_student(self): student = Student(1, "Zach") yield student
def test_alter_stu_name_false(self, valid_student): new_name = "ZacharyBlock" expected_result = False
actual_result = valid_student.alter_name(new_name)
assert actual_result == expected_result
|
- 使用
pytest.fixture实现一个fixture引用另一个fixture
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import pytest
from myProject.pytest_fixture.Student import Student
class TestStudent: @pytest.fixture def valid_student(self): student = Student(1, "Zach") yield student
@pytest.fixture def invalid_student(self, valid_student): valid_student.name = "ZacharyBlock" yield valid_student
def test_valid_name_false(self, invalid_student): expected_result = False
actual_result = invalid_student.is_valid_name()
assert actual_result == expected_result
|
- 使用
pytest.fixture实现引用多个fixture
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import pytest
from myProject.pytest_fixture.Student import Student
class TestStudent: @pytest.fixture def valid_student(self): student = Student(1, "Zach") yield student
@pytest.fixture def invalid_student(self): student = Student(1, "ZacharyBlock") yield student
def test_valid_student(self, valid_student, invalid_student): expected_result_valid = True expected_result_invalid = False
actual_result_valid = valid_student.is_valid_name() actual_result_invalid = invalid_student.is_valid_name()
assert expected_result_valid == actual_result_valid \ and expected_result_invalid == actual_result_invalid
|
conftest.py
conftest.py使得复用pytest.fixture成为可能
首先在 myProj 文件夹下创建一个 pytest_conftest/student.py
1 2 3 4 5 6 7 8
| class Student: def __init__(self, id, name, gender): self.id = id self.name = name self.gender = gender
def vaild_gender(self) -> bool: return self.gender and self.gender.casefold() in ["male", "female"]
|
接着在,myTest 下创建一个 pytest_conftest/male_student_fixture.py
1 2 3 4 5 6 7 8 9
| import pytest
from myProject.pytest_conftest.student import Student
@pytest.fixture def male_student_fixture(): student = Student(1, "Zachary", "male") yield student
|
为了使得该male_student_fixture可以得到多个模块的复用
需要在myTest文件目录下创建一个conftest.py文件
1
| from myTest.pytest_conftest.male_student_fixture import male_student_fixture
|
在这之后 任何需要使用male_student_fixture的模块,可以直接使用
1 2 3 4 5 6 7 8 9 10
| class TestStudentGender: def test_valid_gender_true(self, male_student_fixture): expected_result = True
actual_result = male_student_fixture.vaild_gender()
assert expected_result == actual_result
|
需要使用命令行运行 不知道为什么 pycharm 直接运行会有问题
1 2 3 4 5 6 7 8 9 10
| (venv) ➜ pythonUnitTest pytest -v myTest/pytest_conftest/test_student_gender.py ====================================================== test session starts ======================================================= platform darwin -- Python 3.7.7, pytest-7.4.4, pluggy-1.2.0 -- /Users/zachary/Documents/PythonCode/pythonUnitTest/venv/bin/python cachedir: .pytest_cache rootdir: /Users/zachary/Documents/PythonCode/pythonUnitTest collected 1 item
myTest/pytest_conftest/test_student_gender.py::TestStudentGender::test_valid_gender_true PASSED [100%]
======================================================= 1 passed in 0.01s ========================================================
|
测试用例—参数化
- 使用
parameterized实现
pip install parameterized
- 实现一个判断数字是否为奇数的例子
1 2 3
| class Judge: def is_odd(self, num: int) -> bool: return num % 2 != 0
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| from parameterized import parameterized
from myProject.pytest_parameterized.judge import Judge
class TestJudge: @parameterized.expand([[1, True], [2, False], [3, True]]) def test_is_odd(self, num, expected_result): judge = Judge()
actual_result = judge.is_odd(num)
assert expected_result == actual_result
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import pytest
from myProject.pytest_parameterized.judge import Judge
class TestJudge: @pytest.mark.parametrize("num,expected_result", [(1, True), (2, False), (3, True)]) def test_is_odd(self, num, expected_result): judge = Judge()
actual_result = judge.is_odd(num)
assert expected_result == actual_result
|
更新: 2024-01-23 06:38:01
原文: https://www.yuque.com/zacharyblock/cx2om6/dzbxl6wz2ns1pgpi
Author:
Zachary Block
Permalink:
http://blockzachary.cn/blog/4042161217/
License:
Copyright (c) 2019 CC-BY-NC-4.0 LICENSE