首页 / 资讯中心 / 文章详情

Faker库实战:让接口测试与自动化测试的造数更真实高效

Faker库实战:让接口测试与自动化测试的造数更真实高效 ★ FEATURED ARTICLE
做接口测试和自动化测试的朋友应该都体验过那种被“假数据”支配的尴尬。我早年在某项目组做下单流程的接口测试为了凑测试用户手工在配置文件里写了几百个“张三”手机号清一色13800138000地址全是“北京市朝阳区某街道1号”。结果接口联调的时候前端那边一个身份证格式校验就卡住了——“张三”的身份证号随手填的“123456789012345678”压根不满足18位身份证的校验规则下游实名接口直接返回解析失败。排查了大半天最后发现不是被测系统的问题是测试数据本身就有问题。后来我开始在测试项目里引入Faker库用Python生成一批又一批结构真实的随机测试数据姓名、手机号、身份证、地址、邮箱、公司名、订单号、UUID几行代码就能造出成百上千条。这篇文章结合我实际用下来的经验聊聊Faker在软件测试里的核心价值、常用玩法、以及那些容易踩的坑适合正在做接口测试、自动化测试、压测造数的同学参考。1. 为什么测试里会有一堆“假数据”问题Faker要解决的痛点1.1 手工造数据的真实痛点很多测试团队一开始不愿意引入造数工具觉得“造几条测试数据嘛多大点事”。真正接手复杂业务系统之后就会明白手工造数据这件事成本远比想象中高。第一个痛点是字段格式覆盖不全。真实业务里手机号要符合11位且以合法号段开头身份证要满足18位和校验规则邮箱要符合域名格式订单号可能还有固定的前缀和位长。手工填数据时大家普遍图省事写几个“abc123”“123456”就完事。这种数据在简单的增删改查用例里看不出问题一旦被测接口做强校验比如实名认证、风控规则、卡Bin识别大半用例都会挂在“测试数据本身不合法”上而不是被测功能真的有Bug。第二个痛点是数据之间的关联性。真实项目里的用户、订单、地址、支付流水并不是孤立存在的一个用户下了一个订单订单里的收货地址应该和这个用户所在城市匹配手机号归属地也最好别和地址差出十万八千里。手工造数据时通常是一个字段一个字段地复制粘贴造出来的数据彼此之间毫无关联既不像真实用户也不利于校验那些做“信息一致性判断”的业务规则。第三个痛点是数据量远远不够。接口联调、性能压测、报表统计动不动就要几千上万条数据。手工造一百条数据我都嫌手指疼更别说造几万条。很多测试同学最后选择写脚本循环插数据可循环里如果只拿一个“test001”往死里填充造出来的数据分布和真实环境严重不符这种数据做功能测试勉强能用做性能压测和报表测试就完全没有参考价值了。第四个痛点是数据污染和清理困难。手工造的测试数据散落在配置里、数据库里、Excel表里没有统一管理不知道哪些是自己造的、哪些是历史遗留的更不知道能不能删。真要跑回归的时候脏数据混在一起断言怎么也写不对排查半天可能又是被历史垃圾数据影响了。1.2 “假数据”该“假”到什么程度在聊怎么用Faker之前我想先理清一个概念测试数据要的“假”不是随便填一点内容叫假而是“结构真实、内容虚构”。什么意思呢一条合格的测试数据它的手机号应该符合号码格式、邮箱应该符合邮箱语法、身份证应该符合18位结构、日期应该落在合理的业务区间内这就是“结构真实”。但它的具体值又是随机的、可重复生成的、不属于任何真实用户的这就是“内容虚构”。Faker库的价值恰恰就在这里——它不是一个简单的随机字符串生成器而是一个按现实世界规则批量生成“看起来完全真实”数据的工具。我见过有人把Faker理解成“造一批乱码一样的东西”这是不对的。Faker生成的“张三同学”可能是个不存在的虚拟人但“张三”这两个字是符合中文姓名用字习惯的他的手机号码格式是合法的他的地址从省到街道层层递进完全真实甚至他的公司名、职位、邮箱、信用卡号都能串起来。这种“以假乱真”的测试数据才能让被测系统在各种真实约束下跑起来该触发的校验触发、该走的异常分支也都能走得到。我个人对Faker的定位是它把测试人员从“手工编数据”里解放出来把精力放到真正应该关注的测试设计和问题分析上。至于“假数据”到底要假到什么程度核心标准就是一句话——被测系统关心的约束你的数据必须满足被测系统不关心的部分保持随机即可。2. 从names()到json()Faker常用能力与本地化细节2.1 最常用的数据生成API与典型用法Faker的安装很简单pip install Faker即可。引入之后做一个实例化from faker import Faker fake Faker(zh_CN)这里的zh_CN是本地化参数用来告诉Faker生成中文语境下的姓名、地址、手机号、公司名等。如果不传默认生成英文环境的数据。下面这张表整理了我平时最常用到的方法基本都是每个测试项目里跑不掉的字段方法生成内容常见用途fake.name()姓名用户、联系人、实名认证fake.phone_number()手机号注册、下单、通知类用例fake.address()详细地址收货地址、公司注册地址fake.email()邮箱地址登录账号、找回密码fake.id_card_number() / fake.ssn()身份证号实名、风控、银行卡绑定fake.company()公司名企业客户、发票抬头fake.uuid4()UUID字符串主键、流水号、幂等键fake.date_time_this_year()今年内的随机时间订单时间、创建时间fake.pyint(min_value1, max_value100)指定范围整数年龄、数量、分数fake.word() / fake.sentence()词/句子评论内容、标签、搜索词fake.credit_card_number()银行卡号支付、绑卡fake.simple_profile()一组完整用户信息快速构造用户表记录这些方法并不需要全背用多了自然就熟了。我这里特别提醒一句Faker实例化之后尽量复用不要每次生成一个字段就重新Faker(zh_CN)一次那会白白消耗很多构造Provider的开销。通常一个进程里一个全局实例就够用了。2.2 本地化与多语言数据为什么中文化不是加个参数那么简单很多同学觉得自己随便手写几个中文姓名和地址就能替代Faker的中文能力真不是这样。Faker的中文Provider并不是简单把英文名翻译成中文而是按照中文世界的姓名、地理、邮编、公司后缀等规则独立构造的。比如中文姓名它会结合常见姓氏和名字用字生成“李明”“王秀英”这类读起来很正常的中文名中文地址会从省、市、区、街道、门牌号逐级组合生成“XX省XX市XX区XX路XX号”这样完整层级的中文地址手机号也会优先从合法号段里挑。这些细节手工模拟起来很麻烦Faker却能稳定输出。不同国家的Provider差异也很明显。en_US生成的手机号类似(555) 123-4567ja_JP生成的是日本人名和日本地址zh_CN则输出常见中文信息。如果我们的被测系统需要多语言环境适配就可以准备多个实例fake_cn Faker(zh_CN) fake_us Faker(en_US) fake_jp Faker(ja_JP)多语言测试里有个容易忽略的细节某些字段并不是所有locale都支持同名方法。比如fake.ssn()在英文Provider里叫“美国社保号”在中文Provider里是身份证号有些语言环境可能没有指定的公司后缀Provider调用时会报AttributeError。所以项目里最好封装统一入口按环境变量切换而不是在用例代码里散落一堆Faker(en_US)。2.3 从单字段到结构化数据pydict与json实际写自动化测试的时候光会生成单个字段还不够大家通常需要的是“一条完整的数据记录”。Faker已经提供了几个组合方法能把多个字段一次性生成在一个结构里。最常用的就是simple_profile()和pydict()profile fake.simple_profile() print(profile)输出大概是这样一组用户信息用户名、姓名、性别、地址、邮箱、出生日期。对于构造用户表记录来说这一下子就省了好几行代码。如果字段需要自己定义用pydict更灵活user_dict fake.pydict( nb_elements5, variable_nb_elementsFalse, fields{ name: name, phone: phone_number, address: address, created_at: date_time, }, )fields左边是输出字段名右边是Faker的Provider方法名生成的字典结构稳定非常适合直接塞给接口或者插入数据库。如果被测接口接收的是JSON字符串Faker也提供了json()方法不过我个人更倾向先生成Python字典再序列化这样改起来更直观也方便在pydict结果基础上做overrides。3. 实战落地用Faker建一套测试数据工厂并接入自动化流程3.1 数据工厂设计独立模块与统一入口把Faker用好第一步不是急着写用例而是建一个统一的“测试数据工厂”模块。我见过太多项目把fake.name()到处乱写在用例文件里一个用例一个风格今天用中文User明天用英文后天又改成手工overrides维护成本极高。我的习惯是把所有跟测试数据相关的生成函数集中在一个模块里比如test_data_factory.py# test_data_factory.py from faker import Faker _fake Faker(zh_CN) def make_user(**overrides): data { name: _fake.name(), phone: _fake.phone_number(), id_card: _fake.ssn(), email: _fake.email(), address: _fake.address(), created_at: _fake.date_time_this_year().isoformat(), } data.update(overrides) return data def make_order(user_idNone, item_count1): return { user_id: user_id or _fake.uuid4(), order_no: _fake.uuid4().hex[:16].upper(), items: [make_item() for _ in range(item_count)], created_at: _fake.date_time_this_year().isoformat(), } def make_item(): return { sku_id: _fake.uuid4(), name: _fake.word(), price: _fake.pydecimal(left_digits3, right_digits2), quantity: _fake.pyint(min_value1, max_value10), }这样做的好处有三个。第一全局唯一Faker实例性能损耗降到最低第二所有字段规则集中管理想在邮箱后统一追加测试标识、想把手机号限制在测试号段里只需要改一个地方第三用例代码里写make_user(phone13900000000)这样一行语义非常清晰别人一看就知道这条用例要覆盖什么。我强烈建议在工厂函数里都透传overrides参数。自动化测试里总有那么几条用例需要固定某个字段比如实名认证用例要指定身份证风控用例要指定金额如果工厂函数不支持覆盖那这些固定值就很难注入最后只能复制粘贴造一条新函数越搞越乱。3.2 接入Pytest固定装置与作用域数据工厂建好之后下一步就是把它接进自动化框架。在Pytest里最自然的做法是用fixtureimport pytest from faker import Faker from test_data_factory import make_user, make_order pytest.fixture(scopesession) def fake(): return Faker(zh_CN) pytest.fixture def created_user(db_helper, fake): user_data make_user() user_id db_helper.insert_user(user_data) user_data[id] user_id yield user_data db_helper.delete_user(user_id) def test_user_order_flow(created_user): order make_order(user_idcreated_user[id]) # 断言下单成功 assert create_order_api(order)[code] 0created_user这个fixture负责三件事创建数据、交给测试用例、测试结束后清理数据。这里yield之前的代码是前置准备yield之后的delete_user就是清理动作。Pytest会自动保证用例结束后执行清理就算用例中途断言失败清理也会照常执行这一点比在用例里写try/finally干净得多。fixture的scopesession表示整个测试会话只初始化一次Faker实例这样几千条用例跑下来不会反复重建对象。数据处理相关的fixture一般用函数级作用域即可确保每条用例拿到独立数据互不干扰。3.3 真实项目中的造数与清理闭环有了数据工厂还要解决一个关键问题测试数据入库之后怎么清理。最理想的做法是让测试用例全程使用事务一个用例开启一个事务用例跑完直接rollback数据根本不会真正落库。但在很多项目里被测服务可能跨多个服务、多个数据库事务很难覆盖全链路。我常用的替代方案是给测试数据加统一标识。具体做法是在生成数据时把一条测试记录里的某个字段设置为当前测试运行ID比如email fauto_{run_id}_{random_string}example.com或者给订单号加固定前缀“AUTO”。清理时直接按前缀或运行ID批量删除DELETE FROM users WHERE email LIKE auto_20240901_%; DELETE FROM orders WHERE order_no LIKE AUTOTEST%;这样做的好处是即使某条用例清理失败后续也能通过一次SQL把遗留数据清干净。我在项目里还会专门写一个“数据巡检”脚本定期扫描这些前缀字段有没有越积越多有就提醒负责人处理。这里多说一句如果你只是跑本地开发环境或者测试环境数据清理规则可以松一点积压几天没问题但如果测试环境要和多方共享或者要跑长期回归清理必须做到“每次运行结束不留尾巴”否则你永远不会知道某条失败用例是不是被上一轮测试的数据干扰的。4. 随机数据的坑可复现性、关联性与性能陷阱4.1 随机造数导致用例“时好时坏”Faker最大的特点就是“随机”但随机在自动化测试里是把双刃剑。我遇到过A同学写了个注册接口用例每次都调用fake.user_name()生成用户名。表面看没问题可某天CI突然挂了一条用例提示注册失败看起来像是接口异常。本地复现却无论如何都跑不出同样结果——因为每次随机出来的用户名不一样有时生成一个特别长的字符串接口校验没过有时又恰好生成了正常的用户名用例就通过了。这就是典型的“偶发失败”。RANDOM数据让用例失去可复现性出了问题连现场都还原不了非常折磨人。解决思路也很简单在用例里固定随机种子。def test_user_register(seed20240901): fake Faker(zh_CN) fake.seed_instance(seed) username fake.user_name() # 使用相同的seed每次运行生成的username序列完全一致seed_instance是Faker推荐的实例级种子设置方法。只要种子相同、调用顺序相同生成的数据序列就完全一致。这样一来用例在CI跑挂了本地用同一个种子重跑能立刻还原出一样的输入数据排查效率高一个量级。关于种子控制我的策略放到下一节详细说这里先记住一个核心原则在需要稳定复现的用例里固定种子在需要探索边界的场景里放开随机。4.2 关联字段不一致造出来的用户不像同一个人随机数据的另一个大坑是字段之间互相“打架”。你拿fake.name()生成一个中文姓名拿fake.email()生成一个英文乱序邮箱拿fake.phone_number()生成一个号段归属地和地址城市完全不符的手机号这些值单个看都合法拼在一起却完全不像同一个人。有些业务系统的校验是跨字段的比如用户在绑卡的时候系统会比对银行卡归属地和用户身份证归属城市是否一致下单的时候风控会检查收货手机号归属地和收货地址是否匹配。如果测试数据里这些字段相互矛盾被测系统很容易把测试数据当成风险事件拦截掉返回“风控拒绝”“信息不匹配”之类的结果用例失败后你还得逐字段检查看看是不是数据自己闹别扭了。解决方案是设计字段之间依赖关系。我通常会把用户生成逻辑做成一个整体而不是每个字段独立随机def make_user(fake): first_name fake.last_name() given_name fake.first_name() full_name f{first_name}{given_name} # 邮箱基于姓名拼音和数字保证和姓名有语义关联 email f{first_name}{given_name}{fake.pyint(1, 999)}example.com phone fake.phone_number() address fake.address() # 还可以根据address里的城市做手机号前缀匹配 return { name: full_name, email: email, phone: phone, address: address, }其实Faker的simple_profile()内部已经做了很多字段关联用户名、姓名、性别、地址、邮箱、出生日期都是相互自洽的能解决大部分场景。如果业务有更复杂的依赖就自己封装工厂函数把“看起来是一个真人”作为造数标准。4.3 批量造数与生成速度滥用循环的实测教训刚开始用Faker批量造数的时候我犯过一个低级错误在循环里反复实例化Faker对象。造一万条数据循环一万次Faker(zh_CN)速度慢到怀疑人生。其实Faker每次实例化都要初始化一堆Provider这个开销非常可观正确做法是先建好实例再在循环里调用方法from faker import Faker from test_data_factory import make_user fake Faker(zh_CN) records [make_user(fake) for _ in range(10000)]十万条数据规模下这个差异尤其明显。反复实例化能轻松跑到几十秒而复用单实例通常几秒就完成。另一个性能细节是unique唯一值限定。fake.unique.phone_number()能保证一批数据里的手机号不重复这个特性在造主键类数据时很好用但它内部会维护一个去重集合生成数量越大碰撞检测越耗时。如果不做严格约束后期会明显变慢甚至抛UniqueProxyException。我自己的经验是只在必须唯一的关键字段上使用比如身份证、订单号、UUID其他字段不要滥用。最后再说一个内存问题。批量造数时大家习惯[make_user() for _ in range(n)]一次生成一个大列表数据量小无所谓但如果一造就是几十万条列表占用的内存就很可观。后面第5章我会给一个分批生成的小方案。4.4 生成的“真实数据”可能踩红线隐私与外部触达Faker生成的数据虽然大多来自随机组合但有一类数据要格外小心那就是手机号、身份证号、银行卡号这一类“具有真实号码特征”的信息。Faker底层的号段数据基于现实规则随机组合出来的号码有概率命中真实存在的号码资源只是你不知道它属于谁。在测试环境里用这类数据最大的风险不是造出来不对而是可能产生“外部触达”。比如测试用例里如果用生成的手机号去调真实的短信发送通道系统可能真的往一个随机手机号发出一条验证码短信这就是典型的测试事故。我的处理原则是凡是会触发真实短信、邮件、语音通知的测试一律不用Faker原生生成的号码和邮箱必须用专门的测试号段和保留域名。邮箱推荐统一使用example.com这类保留域名号码则可以在工厂函数里做一层包装def make_test_phone(): # 使用专门测试号段 170/171并在项目文档中声明不要接通真实短信网关 return f1700000{_fake.random_int(min1000, max9999)}这条经验不是危言耸听我在实际项目里就见过测试数据误触真实业务的案例处理起来非常麻烦。规范造数规则的意义不只是“让数据好看”更是防止测试行为外溢到真实环境。5. 往深走种子控制、数据结构化与批量造数的进阶优化5.1 种子控制让“随机”在需要时可复现上一节提过seed_instance的用法这一节我想把种子策略展开说清楚因为这是我用Faker这几年觉得最值得花时间设计的地方。先放一张常用种子策略对照表方便不同场景按需取用策略适用时机注意点不设种子探索性测试、随机边界扫描失败难以复现不建议用于CI全局固定种子每日回归、冒烟测试直观可控但多用例并行可能互相影响每个用例独立种子正式自动化用例单用例稳定复现又保留一定随机性按需放开随机针对边界值、异常值专项测试记录失败现场的种子便于debug我个人最常用的是“每个用例独立种子”在用例的fixture里接收一个seed参数默认给一个基于用例名和当前运行日期的固定值只有专门做随机探索的用例才显式放开。pytest.fixture def fake(seed): faker Faker(zh_CN) faker.seed_instance(seed) return faker这样做的好处是某条用例跑挂了日志里能看到这条用例自己的种子值拉出来就能重建数据而其他用例的数据又各不相同能覆盖更广的字段分布。如果团队还没有统一的随机管理策略我建议从这一步做起。有一点要说明并行执行环境下固定种子只能保证“单独重跑这条用例”时序列一致如果多个线程同时操作同一个Faker实例结果还是会乱。所以并发测试中要么每个线程单独创建Faker实例并各自设种子要么就不要指望并发下种子可控。5.2 自定义Provider造业务专有的数据规则业务系统里总有那么几个字段是Faker原生Provider覆盖不到的比如内部工号要有“C-2024-XXXX”这种固定前缀优惠券编码要符合某种校验位算法测试手机号必须落在规定号段。这种时候不要硬凑Faker提供了自定义Provider机制写起来很清爽from faker.providers import BaseProvider class TestBusinessProvider(BaseProvider): def union_pay_card_no(self): return 62 .join(self.random_int(min0, max9) for _ in range(14)) def employee_no(self): prefix self.random_element([C, T, A]) year self.random_int(min2020, max2026) seq self.random_int(min1000, max9999) return f{prefix}-{year}-{seq} def test_phone(self): return f1700000{self.random_int(min1000, max9999)} fake Faker(zh_CN) fake.add_provider(TestBusinessProvider) print(fake.union_pay_card_no()) print(fake.employee_no()) print(fake.test_phone())自定义Provider最大的好处是能把业务规则固化在测试基础设施层。新同事接手项目不需要翻文档查“工号怎么造”看一眼Provider方法名就知道。这些Provider方法和Faker原生方法平起平坐在pydict、数据工厂里都能直接调用整个造数体系会整齐很多。不要一上来就写特别复杂的自定义Provider先把项目中重复出现三次以上的造数规则抽出来就足够了。规则太多的时候还可以按领域拆成多个Provider类比如用户类、支付类、订单类别堆在一个类里。5.3 批量测试数据生成与渐近式插入策略性能压测和数据报表测试经常需要一次造几万甚至上百万条数据。这时候把数据全部load到内存再插入数据库很容易把测试机内存吃满插入过程也容易因单条insert太慢而卡成蜗牛。我的做法是“生成器 分批提交”import itertools from test_data_factory import make_user def iter_users(fake, n): for _ in range(n): yield make_user(fake) def batch_insert(db, fake, total, batch_size5000): generator iter_users(fake, total) while True: batch list(itertools.islice(generator, batch_size)) if not batch: break db.executemany( INSERT INTO users(name, phone, email, address) VALUES (%(name)s, %(phone)s, %(email)s, %(address)s), batch, ) db.commit()分批插入的好处有两个一是内存里同时只保留batch_size条记录内存占用可控二是executemany批量执行比一条条insert快很多。批次大小我一般压在1000到5000之间太大反而会因为单次事务重构导致数据库压力上升。如果只是本地快速压测不要求数据真实关联还可以稍微牺牲一下字段丰富度只生成必要字段速度会更快。这个取舍没有标准答案取决于测试目标功能测试重字段多样压测重数据量别搞反了。5.4 数据规划的经验沉淀把测试数据当“最小真实系统”来管理Faker用得久了我越来越觉得它只是造数链条上的一环。真正决定测试数据质量的是你有没有依赖一套“最小真实系统”的思路去管理数据。什么叫最小真实系统就是测试数据不需要像生产环境那样完整但被测系统依赖的那一面必须真实。比如下单流程依赖用户、商品、库存、支付那我造数据时就保证这四块数据的目标规则真实、字段关联真实、清理方式可控至于用户有没有头像、商品图片是不是一张图片这些不是被测逻辑关心的Out of scope即可。我实际项目里的数据规划一般分四层全局共享的底层数据比如基础配置、城市字典、测试账号体系用固定的手工种子数据不随机。用例级数据用Faker工厂函数生成字段关联和清理策略都写在fixture里。批量数据用生成器加分批插入用于压测和报表验证。异常数据手工显式构造比如超长字符串、空值、非法编码这类数据一定要稳定可控不能随机。按照这套分层思路Faker的定位也就清晰了它是“用例级数据”和“批量数据”的主力生成引擎而底层种子数据和异常数据仍然需要人工把关。这大概就是我用Faker几年下来最想分享的东西。工具本身很简单文档也写得明明白白真正拉开差距的是怎么把随机、可复现、关联性、清理闭环这些约束放进日常的测试数据管理习惯里。希望你也能在下一个测试项目里把造数这件事从“凑合着来”变成“规划着来”。
阅读完成 · 觉得有帮助?
咨询建站