点击上方蓝色文字关注我

QA Tester
《岁朝清供》
1. 测试工程师必看:RAG与LLM测试,这些核心差异别搞混!
作为测试工程师,你是否也遇到过这些痛点? 手动造测试数据耗时耗力,重复写相似的实例化代码导致脚本臃肿,不同环境切换时测试依赖频繁报错,测试后数据残留导致下次执行失败……尤其是在接口自动化、UI自动化场景中,这些问题往往会拖慢测试进度,甚至影响测试结果的准确性。
今天,就给大家分享一个测试自动化中的“效率神器”——Python工厂函数。它不是什么高深的黑科技,却是能帮你简化代码、统一管理测试依赖、解决数据混乱的关键技巧,无论是新手还是资深测试,学会就能直接落地提效。
很多测试同学一听到“工厂函数”,就联想到复杂的设计模式,其实大可不必。从测试实操角度来说,工厂函数的核心逻辑非常简单:
工厂函数 = 一个“自动生产测试所需对象/数据的函数”。
它封装了对象的创建逻辑,你只需要告诉它“你要什么”(传入简单参数),它就会返回你需要的实例或数据,而你完全不用关心“它是怎么造出来的”。
== 一个例子🌰 ==
我们做接口测试时,经常需要创建用户数据(用户名、手机号、邮箱),如果每次都手动写字典、赋值,不仅重复,还容易出现数据重复、格式错误。而工厂函数,就相当于一个“自动造用户数据的机器”,调用一次就生成一组符合要求的数据,还能灵活调整参数。
这里要区分两个容易混淆的概念,避免踩坑:
① 普通函数:执行特定逻辑,返回结果(如计算两个数的和);
② 工厂函数:专门用于创建对象/数据,返回的是一个“可直接使用的实例或结构化数据”,核心是“生产”,而非“计算”。
Python中最基础的工厂函数就是内置函数,比如 list()、 dict(),你不用关心列表、字典是怎么实现的,调用就会生成对应实例。而我们在自动化测试中,更多是自定义工厂函数,适配自己的业务场景。
在自动化测试中,工厂函数不是“可选项”,而是“提升效率、降低维护成本的必选项”,尤其是在中大型项目中,其优势会被无限放大,具体可以总结为4点.
这是测试工程师最常遇到的痛点:手动造数据耗时,测试后不清理导致数据残留,下次执行用例直接报错。而工厂函数可以结合Faker工具,自动生成唯一、符合业务规则的测试数据,搭配Fixture还能实现测试后自动清理,彻底摆脱手动操作。
比如新手常写的冗余代码(手动造用户),和高手用工厂函数的写法对比,差距一目了然:
# 新手做法:手动造数据,重复且易冲突def test_user_order():# 每次都要手动写数据,手机号、邮箱可能已存在user_data = {"name": "测试用户","phone": "1380000xxxx","email": "test@test.com"}# 调用API...# 测试完数据残留,下次跑用例失败
# 高手做法:工厂函数+Fixture,自动生成+自动清理from faker import Fakerimport pytestfake = Faker('zh_CN') # 支持中文数据生成# 定义用户数据工厂class UserFactory:@classmethoddef create(cls, **kwargs):# 基础数据,确保唯一base_data = {"username": fake.user_name() + str(random.randint(1000, 9999)),"email": fake.unique.email(),"phone": fake.phone_number()}# 支持自定义参数覆盖,灵活适配不同场景return {**base_data, **kwargs}# Fixture管理数据生命周期,测试后自动清理@pytest.fixturedef clean_user():user = UserFactory.create()# 模拟插入数据库user_id = db.insert(user)yield user # 测试用例执行期间使用该用户db.delete(user_id) # 测试结束,自动清理数据# 测试用例直接注入使用,无需手动造数据def test_user_order(clean_user):response = api.create_order(clean_user)assert response.ok # 测试结束,数据自动消失
自动化测试中,很多测试用例需要重复创建相似的对象(比如不同场景的用户、订单、商品)。如果每个用例都写一遍实例化代码,一旦业务规则变化(比如用户新增“性别”字段),就需要修改所有相关用例,维护成本极高。
工厂函数将对象创建逻辑集中管理,所有用例都通过调用工厂函数获取实例。一旦业务规则变化,只需要修改工厂函数的代码,所有用例自动适配,极大减少重复工作。
工厂函数可以屏蔽对象创建的复杂细节,测试用例只需要关注“测试逻辑”,不需要关心“依赖怎么来的”。比如测试不同数据库环境的接口时,只需要修改工厂函数的参数,就能切换数据库实例,无需修改测试用例本身。 比如多数据库适配场景,用工厂函数可以轻松实现切换:
# 数据库连接工厂,支持多环境切换class DBFactory:@staticmethoddef create_db(kind: str, **cfg):if kind == "mysql":return MySQL(**cfg) # MySQL数据库实例if kind == "pg":return Postgres(**cfg) # Postgres数据库实例raise ValueError(f"不支持的数据库类型:{kind}")# 测试环境切换,只需修改参数# 测试MySQL环境mysql_db = DBFactory.create_db("mysql", host="127.0.0.1", port=3306)# 测试Postgres环境pg_db = DBFactory.create_db("pg", host="127.0.0.1", port=5432)
自动化测试中,同一套测试逻辑可能需要适配不同场景(比如不同角色的用户、不同浏览器的UI测试、不同量级的性能测试数据)。工厂函数可以通过传入不同参数,动态生成不同的实例,完美适配多场景测试,无需重复编写测试脚本。
结合测试工作中的实际需求,整理了4个最常用的场景,每个场景都有完整可运行的代码,复制就能落地使用,覆盖接口测试、UI测试、性能测试等常见场景。
接口测试中,用户、订单、商品等数据需要保证唯一性(避免重复注册、重复创建),用工厂函数+Faker工具,可以一键生成符合业务规则的唯一数据,搭配PytestFixture实现数据自动清理,解决数据残留问题。
# 安装依赖:pip install faker pytest requestsfrom faker import Fakerimport pytestimport requestsimport random# 初始化Faker,生成中文数据fake = Faker('zh_CN')# 1. 定义用户数据工厂(适配用户相关接口)class UserFactory:@classmethoddef create(cls, role="普通用户", **kwargs):"""生成用户数据,支持自定义角色和其他参数:param role: 角色(普通用户/管理员/游客):param kwargs: 自定义参数,用于覆盖默认值:return: 结构化用户数据"""# 基础数据(确保唯一)base_user = {"username": fake.user_name() + str(random.randint(1000, 9999)),"password": fake.password(length=10, special_chars=True),"email": fake.unique.email(),"phone": fake.phone_number(),"role": role,"register_time": fake.date_time_this_year().strftime("%Y-%m-%d %H:%M:%S")}# 允许自定义参数覆盖(比如指定手机号、邮箱)return {**base_user, **kwargs}# 2. 定义Fixture,管理用户数据生命周期@pytest.fixture(scope="function") # 每个用例独立,避免数据污染def unique_user():"""生成唯一用户,测试后自动删除(模拟接口删除)"""# 生成用户数据user = UserFactory.create()# 调用注册接口,创建用户register_url = "http://xxx.xxx.xxx/api/user/register"requests.post(register_url, json=user)yield user # 测试用例使用该用户# 测试结束,调用删除接口,清理数据delete_url = f"http://xxx.xxx.xxx/api/user/delete?username={user['username']}"requests.post(delete_url)# 3. 测试用例(直接注入用户,无需手动造数据)def test_user_profile(unique_user):"""测试用户资料查询接口"""url = "http://xxx.xxx.xxx/api/user/profile"headers = {"Authorization": f"Bearer {unique_user['token']}"} # 假设返回tokenresponse = requests.get(url, headers=headers)# 断言:查询到的用户名与工厂生成的一致assert response.json()["username"] == unique_user["username"]assert response.status_code == 200
UI自动化测试中,需要适配 Chrome、Firefox、Edge 等多种浏览器,用工厂函数可以统一管理浏览器实例的创建和关闭,避免重复编写浏览器初始化代码,还能灵活切换浏览器类型。
# 安装依赖:pip install selenium webdriver-managerfrom selenium import webdriverfrom webdriver_manager.chrome import ChromeDriverManagerfrom webdriver_manager.firefox import GeckoDriverManagerfrom webdriver_manager.microsoft import EdgeChromiumDriverManager# 浏览器实例工厂class BrowserFactory:@staticmethoddef create_browser(browser_type="chrome"):"""生成浏览器实例,支持多浏览器切换:param browser_type: 浏览器类型(chrome/firefox/edge):return: 浏览器实例"""if browser_type.lower() == "chrome":# 初始化Chrome浏览器driver = webdriver.Chrome(ChromeDriverManager().install())elif browser_type.lower() == "firefox":# 初始化Firefox浏览器driver = webdriver.Firefox(GeckoDriverManager().install())elif browser_type.lower() == "edge":# 初始化Edge浏览器driver = webdriver.Edge(EdgeChromiumDriverManager().install())else:raise ValueError(f"不支持的浏览器类型:{browser_type}")# 统一设置浏览器窗口大小driver.maximize_window()return driver# 测试用例(灵活切换浏览器)def test_baidu_search():# 1. 生成Chrome浏览器实例driver = BrowserFactory.create_browser("chrome")try:driver.get("https://www.baidu.com")driver.find_element(By.ID, "kw").send_keys("Python工厂函数 自动化测试")driver.find_element(By.ID, "su").click()# 断言:搜索结果包含关键词assert "Python工厂函数" in driver.titlefinally:# 关闭浏览器driver.quit()# 切换到Firefox浏览器,只需修改参数def test_baidu_search_firefox():driver = BrowserFactory.create_browser("firefox")try:driver.get("https://www.baidu.com")# 后续测试逻辑相同...finally:driver.quit()
性能测试中,常常需要批量生成海量测试数据(比如1000个用户、10000条订单),用于测试接口的并发处理能力。用工厂函数可以批量生成数据,搭配Fixture控制数据生命周期,避免测试后数据库冗余。
import pytestimport randomfrom faker import Fakerfake = Faker('zh_CN')# 批量用户数据工厂class BulkUserFactory:@classmethoddef create_bulk_users(cls, count=10, prefix="perf_"):"""批量生成用户数据:param count: 生成数量:param prefix: 用户名前缀,用于区分性能测试数据:return: 用户列表"""users = []for i in range(count):user = {"username": prefix + fake.user_name() + str(i),"email": fake.unique.email(),"phone": fake.phone_number(),"password": fake.password()}users.append(user)return users# Fixture:预生成1000个用户,供所有性能测试用例共享@pytest.fixture(scope="session") # 全局只创建一次def bulk_test_users():"""性能测试:预生成1000个用户,测试后统一清理"""print("⏳ 正在生成1000个性能测试用户...")users = BulkUserFactory.create_bulk_users(count=1000, prefix="perf_")# 批量插入数据库db.bulk_insert(users)yield users # 所有性能测试用例共享该用户列表# 测试结束,批量清理数据print("🧹 正在清理性能测试数据...")usernames = [user["username"] for user in users]db.bulk_delete(usernames)# 性能测试用例:测试批量查询接口性能def test_bulk_user_query(bulk_test_users):"""测试批量查询1000个用户的响应时间"""url = "http://xxx.xxx.xxx/api/user/bulk_query"params = {"usernames": [user["username"] for user in bulk_test_users[:100]]}response = requests.get(url, params=params)# 断言:响应时间小于1秒,接口正常返回assert response.elapsed.total_seconds() < 1.0assert response.status_code == 200assert len(response.json()["data"]) == 100
很多业务流程测试(比如订单支付流程)需要依赖多个关联数据(先有用户、再有商品、最后创建订单),用工厂函数可以生成完整的依赖链数据,确保测试流程的连贯性,同时避免重复创建依赖。
import pytestimport random# 1. 商品数据工厂class ProductFactory:@classmethoddef create(cls, **kwargs):base_product = {"product_id": f"PROD{random.randint(100000, 999999)}","product_name": fake.product_name(),"price": round(random.uniform(10.0, 1000.0), 2),"stock": random.randint(10, 1000)}return {**base_product, **kwargs}# 2. 订单数据工厂(依赖用户和商品)class OrderFactory:@classmethoddef create(cls, user, product, **kwargs):"""创建订单,依赖用户和商品数据"""base_order = {"order_no": f"ORDER{random.randint(1000000, 9999999)}","user_id": user["username"],"product_id": product["product_id"],"order_amount": product["price"],"create_time": fake.date_time_this_hour().strftime("%Y-%m-%d %H:%M:%S")}return {**base_order, **kwargs}# 3. Fixture:生成订单依赖链(用户→商品→订单)@pytest.fixture(scope="class") # 整个测试类共享,避免重复创建def order_chain():"""创建订单依赖链,测试后反向清理"""# 1. 创建用户user = UserFactory.create()db.insert_user(user)# 2. 创建商品product = ProductFactory.create()db.insert_product(product)# 3. 创建订单order = OrderFactory.create(user=user, product=product)db.insert_order(order)# 返回完整依赖链yield {"user": user, "product": product, "order": order}# 测试结束,反向清理(先删订单,再删商品,最后删用户)db.delete_order(order["order_no"])db.delete_product(product["product_id"])db.delete_user(user["username"])# 订单流程测试类class TestOrderFlow:"""测试订单完整流程:创建→支付→查询"""def test_create_order(self, order_chain):"""测试创建订单接口"""# 直接使用order_chain中的订单数据response = requests.post("http://xxx.xxx.xxx/api/order/create", json=order_chain["order"])assert response.status_code == 200assert response.json()["order_no"] == order_chain["order"]["order_no"]def test_pay_order(self, order_chain):"""测试订单支付接口"""pay_data = {"order_no": order_chain["order"]["order_no"],"pay_amount": order_chain["order"]["order_amount"],"pay_type": "wechat"}response = requests.post("http://xxx.xxx.xxx/api/order/pay", json=pay_data)assert response.json()["status"] == "paid"
虽然工厂函数好用,但在实际使用中,很多测试同学会踩一些坑,导致脚本出现问题,这里整理了4个常见坑点和解决方案:
① 坑1:数据不唯一,导致测试用例失败
解决:使用 Faker的 unique属性(如 fake.unique.email()),确保生成的邮箱、手机号等数据唯一;同时在工厂函数中加入随机后缀(如用户名+随机数),进一步避免重复。
② 坑2:工厂函数分支过多,维护困难
解决:如果需要生成的对象类型超过3-4种(比如多种数据库、多种角色),不要用 if-elif 分支,改用“注册表模式”,新增类型只需添加注册装饰器,无需修改工厂函数本身。
# 注册表模式优化多类型工厂_REGISTRY = {}# 注册装饰器def register_db(name: str):def deco(cls):_REGISTRY[name] = clsreturn clsreturn deco# 注册不同数据库类@register_db("mysql")class MySQL:pass@register_db("pg")class Postgres:pass# 工厂函数,无需分支判断def make_db(kind: str, **cfg):try:return _REGISTRY[kind](**cfg)except KeyError:raise ValueError(f"不支持的数据库类型:{kind},可选类型:{list(_REGISTRY.keys())}")
③ 坑3:测试数据未清理,导致环境污染
解决:结合Pytest Fixture的yield语法,在工厂函数生成数据后,通过Fixture实现“测试后自动清理”;对于批量数据,使用批量删除接口,提高清理效率。
④ 坑4:工厂函数参数过多,调用繁琐
解决:合理设置默认参数,常用参数在工厂函数中设置默认值,调用时只传入需要修改的参数;对于复杂参数,可使用字典封装,简化调用。
总结:工厂函数=自动化测试的“效率加速器”
作为测试工程师,我们的核心目标是“高效、稳定地完成测试工作”,而工厂函数正是帮我们实现这一目标的关键工具。
它不需要你掌握复杂的设计模式,核心就是“封装创建逻辑、统一管理依赖、减少重复代码”,无论是接口测试、UI测试,还是性能测试,都能发挥巨大作用——从解决“数据地狱”,到简化脚本维护,再到适配多场景测试,工厂函数能帮你节省大量时间,让你专注于测试逻辑本身。

软件测试QA的碎碎念
测无止境
软件测试|总结 | python学习

识别二维码,关注我