要确保app埋点数据的准确性涉及到了测试人员工作的多个方面,埋点数据的准确性贯穿于产品生命周期的各个环节,对产品迭代、用户体验优化、运营策略制定等多个方面都会产生深远影响。以下是APP埋点测试重要性的几个关键点:
测试流程
简易流程图
当前,众多企业在软件开发生命周期中采纳的埋点测试流程大致遵循以下步骤。
值得注意的是,在应用程序的开发与维护中,尽管埋点测试的重要性不容小觑,其紧迫性相较于核心功能测试而言相对较低。换言之,业界普遍接受在产品初期阶段埋点可能存在一定程度的缺陷,但这并不妨碍其作为数据驱动决策基石的关键作用。为了全面洞察并精确测试埋点机制,我们需深入理解并严格遵循以下流程原则,确保每一环节的精准执行与高效产出。
理解业务需求:
深入了解业务目标和埋点数据的用途,确保埋点设计与业务目标对齐。
详细制定埋点规范:
- 参与制定详细的埋点规范文档,测试团队需要深入了解业务目标和埋点数据的用途,这有助于确保埋点规范与业务需求保持一致,与业务需求不一致的部分可以共同讨论解决
- 确定需要追踪的用户行为和事件,确保每个埋点事件都有明确的触发条件和预期结果
- 规范中应包含所有数据字段的详细描述,包括字段名、数据类型、数据来源、数据格式等,这有助于确保数据的标准化和一致性
- 在制定规范时,测试团队应该考虑到未来测试用例的设计,确保规范中包含足够的信息来支持全面的测试覆盖
- 规范中应包括对异常情况的处理说明,如网络中断、用户取消操作等,以及如何处理边界条件,确保数据在非正常情况下也能被正确记录
设计测试用例:
根据埋点规范设计全面的测试用例,覆盖所有埋点事件及其边缘情况。 包括正面案例和负面案例,确保所有可能的交互都被测试到。
- 覆盖边缘情况,针对涉及数值或列表长度的埋点数据,测试边界值和边界外的值,确保数据处理的正确性;如果应用支持多用户同时操作,测试在高并发场景下埋点数据的上报情况
- 正面案例,为每个埋点事件设计正常的用户交互流程,验证数据是否按预期上报
- 负面案例,设计异常情况下的用户行为,如网络中断、输入无效数据、超时等,测试埋点的健壮性和错误处理能力
模拟用户行为:
手动或使用自动化测试工具模拟用户在应用中的各种操作,触发埋点事件。 验证埋点事件是否按预期触发,数据是否正确上报。
- 手动模拟,遵循设计好的测试用例,模拟用户的各种操作,在操作过程中,仔细观察埋点事件是否触发,数据是否按预期上报,并记录测试结果
- 自动模拟,使用测试框架和语言(如Python、Java)编写测试脚本,通过selenium、appium的能力模拟用户操作
数据校验:
使用数据库查询或数据分析工具,检查埋点数据的完整性和准确性。 检查数据字段是否齐全,数值是否合理,是否有异常数据或数据缺失。
- 确定哪些字段是必须的,哪些是可选的,确保所有必需字段都存在,
- 定义每个字段的正确格式,如日期格式、数字精度、字符串长度等
- 设定数值字段的合理范围,避免极端或不合理值
对比测试:
将埋点数据与日志文件、后端数据或其他数据源进行对比,确保数据一致性。 分析数据差异,如事件ID与用户ID的匹配,确保数据的一致性和逻辑性,找出可能的错误源头。
环境测试:
在不同的环境和设备上测试埋点,包括PC、移动设备、不同浏览器和操作系统。 确保在所有支持的环境中埋点数据都能正确收集。
性能测试:
检查埋点上报对应用性能的影响,确保埋点不会造成明显的性能下降。 监控埋点上报的延迟和成功率,优化数据传输策略。
自动化测试:
开发自动化测试脚本来持续验证埋点数据的准确性。 集成到持续集成/持续部署(CI/CD)流程中,确保每次代码更改后都进行测试。
持续监控和优化:
实施持续监控机制,监控埋点数据的实时质量和性能,定期进行回归测试,防止潜在问题的积累,定期审核埋点数据,根据业务需求和技术进步进行优化和调整。
数据校验自动化
埋点上报流程图
先来看一下行业内广泛采纳的几种典型埋点接入方式
- SDK集成:通过嵌入数据服务提供商的软件开发工具包(SDK),实现应用程序与数据收集平台的无缝对接。此方法通常提供高度定制化的数据上报能力,同时保障了数据传输的安全与稳定。
- API调用:利用RESTfulAPI接口,直接从应用程序内部发起数据上报请求。这种方式灵活性较高,适用于对数据上报有特殊需求或复杂逻辑的应用场景。
- 标签管理:借助标签管理系统(Tag Management System, TMS),无需直接修改代码即可实现埋点的添加、删除与修改。这种方法降低了技术门槛,提升了埋点配置的便捷性与响应速度。
从用户交互到数据存储,有如下简易的流程图
数据校验思路
深入剖析埋点上报的全链路,我们得以洞悉软件测试环节在确保数据质量与系统稳定性方面的决定性影响力。尤其在埋点触发逻辑与数据上报至后端服务器这两个关键节点。
采用自动化手段来优化数据上报至后端的校验流程,不仅能够显著提升测试效率,还能增强数据核对的精准度,从而为数据驱动的决策提供更加可靠的支持。以下是一种自动化思路的具体实施方案:
如何捕获数据
- 使用代理服务器:如mitmproxy、Fiddler、Charles等工具,它们可以拦截并记录客户端与后端之间的所有HTTP(S)请求和响应,便于后续分析和校验。
- 日志文件监控:在后端服务器上设置日志记录机制,捕获所有接收到的埋点数据上报请求,包括请求头、请求体、响应状态码等信息。
校验什么数据
- 数据完整性:检查上报数据中是否包含了所有预期的字段,如事件ID、时间戳、用户ID、设备信息等
- 数据格式正确性:验证数据字段的类型和格式是否符合预设规范,如日期格式、数值范围等
- 数据一致性:比对上报数据与业务逻辑或用户行为预期是否一致,如事件触发条件、数据间的逻辑关系等
- 数据时效性:评估数据从客户端上报至后端的时间延迟,确保数据的实时性
校验逻辑是什么
- 自动化脚本:利用Python、JavaScript等编程语言编写自动化脚本,根据预定义的规则和标准,自动执行数据校验任务
- 规则引擎:设计一套规则引擎,将数据校验逻辑抽象成一系列可配置的规则,便于灵活调整和扩展
期望达到什么效果
- 提高测试效率:通过自动化工具和脚本,批量处理数据校验任务,减少手动核对的工作量
- 增强数据质量:自动化校验能减少人为错误,确保数据的准确性和一致性
- 快速反馈机制:及时发现并定位数据上报过程中的问题,缩短问题解决周期
未来收益是什么
- 持续集成与监控:将自动化数据校验集成到持续集成/持续部署(CI/CD)流程中,确保数据质量的实时监控
- 成本节约:减少因数据质量问题导致的返工和资源浪费,长期来看能够显著降低运维成本
- 决策支持:提供高质量的数据基础,助力企业进行更精准的数据分析和业务决策。 通过实施上述自动化策略,不仅能大幅提升数据校验的效率和精准度,还能为企业的数据治理和业务发展带来长远的积极影响
自动化测试方案
mitmproxy代理神器
MITMProxy允许用户在请求或响应到达目的地之前对其进行拦截和修改,非常适合用于测试API接口、验证数据上报逻辑。
访问 mitm.it 它的证书并下载浏览器对应证书
python过滤配置
创建一个名为 filter_api_requests.py 的 Python 文件,并在其中写入以下代码:
from mitmproxy import ctx
def request(flow):
# 检查请求的URL中是否包含"api"
if "api" in flow.request.pretty_url:
ctx.log.info("请求包含 'api': %s" % flow.request.pretty_url)
# 在这里可以对请求进行进一步的处理或修改
启动 mitmdump 打开终端或命令行窗口,使用以下命令启动 mitmdump,并将之前编写的 Python 脚本作为参数传入:
mitmdump -s filter_api_requests.py
配置代理 在你的浏览器或需要测试的应用中,配置HTTP代理为 mitmdump 的监听地址和端口。默认情况下,MITMProxy 监听在本地主机的8080端口,即 127.0.0.1:8080。 发送请求 现在,当你通过配置了代理的浏览器或应用发送网络请求时,所有包含 "api" 的请求都会被 mitmdump 捕获,并由你的 Python 脚本进行处理。你将在终端中看到类似这样的输出:
info: 请求包含 'api': https://example.com/api/v1/data
数据存储逻辑
创建一个 Python 脚本来处理 mitmdump 捕获的请求,并将所需的数据提取出来,然后存储到数据库中
import sqlite3
from mitmproxy import ctx
# 数据库连接初始化
conn = sqlite3.connect('buried_points.db')
cursor = conn.cursor()
# 创建数据表
cursor.execute('''
CREATE TABLE IF NOT EXISTS buried_points (
timestamp TEXT,
userid TEXT,
property_name TEXT,
field_name TEXT,
data_type TEXT,
value TEXT
);
''')
# 提交创建表的命令
conn.commit()
def request(flow):
# 检查请求是否为埋点数据上报
if "buried_point_api" in flow.request.url:
try:
# 解析JSON数据
data = flow.request.json()
# 假设数据结构如下:
# {
# "timestamp": "2023-04-01T12:00:00",
# "userid": "user123",
# "properties": [
# {"name": "property1", "fields": {"field1": "value1", "field2": "value2"}},
# {"name": "property2", "fields": {"field1": "value1"}}
# ]
# }
timestamp = data["timestamp"]
userid = data["userid"]
for prop in data["properties"]:
property_name = prop["name"]
fields = prop["fields"]
for field_name, value in fields.items():
# 插入数据到数据库
cursor.execute('''
INSERT INTO buried_points (timestamp, userid, property_name, field_name, data_type, value)
VALUES (?, ?, ?, ?, ?, ?)
''', (timestamp, userid, property_name, field_name, type(value).__name__, str(value)))
# 提交数据插入操作
conn.commit()
except Exception as e:
ctx.log.error(f"Error processing request: {e}")
# 关闭数据库连接
def done():
conn.close()
SQL 创建表语句如下:
CREATE TABLE IF NOT EXISTS buried_points_data (
id INTEGER PRIMARY KEY AUTOINCREMENT,
report_time TIMESTAMP NOT NULL,
user_id VARCHAR(255) NOT NULL,
property_name VARCHAR(255) NOT NULL,
field_name VARCHAR(255) NOT NULL,
data_type VARCHAR(50) NOT NULL,
field_value TEXT
);
在这个表结构中:
id 是主键,自动递增,用于唯一标识每条记录。 report_time 存储埋点数据的上报时间,使用 TIMESTAMP 类型。 user_id 存储用户ID,VARCHAR 类型,长度为 255,用于存储较长的用户标识。 property_name 和 field_name 分别存储属性名称和字段名称,同样使用 VARCHAR 类型。 data_type 存储字段的数据类型,VARCHAR 类型,长度为 50,足以容纳常见的数据类型名称如 'int', 'float', 'string' 等。 field_value 存储字段的实际值,使用 TEXT 类型,可以存储较大的文本数据。 这个表结构能够满足基本的埋点数据存储需求,同时也方便后续的数据查询和分析。
报告生成
从数据库中读取数据,然后与期望的参考数据进行比较,最后生成报告,注意期望数据需要通过埋点管理平台获取,或者人工手动录入。
import sqlite3
from collections import defaultdict
import datetime
# 数据库连接
conn = sqlite3.connect('buried_points.db')
cursor = conn.cursor()
# 期望数据集
expected_data = [
('2023-04-01T12:00:00', 'user123', 'property1', 'field1', 'str', 'value1'),
('2023-04-01T12:00:00', 'user123', 'property1', 'field2', 'int', '42'),
# 更多数据...
]
# 从数据库中获取数据
cursor.execute('''
SELECT report_time, user_id, property_name, field_name, data_type, field_value
FROM buried_points_data
''')
db_data = cursor.fetchall()
# 数据比较
discrepancies = defaultdict(list)
for exp_item in expected_data:
match_found = False
for db_item in db_data:
if all([exp_item[i] == db_item[i] for i in range(len(exp_item))]):
match_found = True
break
if not match_found:
discrepancies['missing_from_db'].append(exp_item)
for db_item in db_data:
if db_item not in expected_data:
discrepancies['extra_in_db'].append(db_item)
# 数据类型核对
for exp_item in expected_data:
for db_item in db_data:
if exp_item[:5] == db_item[:5]: # 比较除字段值之外的其他元素
if type(exp_item[5]) != eval(db_item[4]):
discrepancies['type_mismatch'].append((exp_item, db_item))
# 报告生成
report = "Data Discrepancy Report\n\n"
if discrepancies['missing_from_db']:
report += "Missing from Database:\n"
for item in discrepancies['missing_from_db']:
report += f"{item}\n"
if discrepancies['extra_in_db']:
report += "\nExtra in Database:\n"
for item in discrepancies['extra_in_db']:
report += f"{item}\n"
if discrepancies['type_mismatch']:
report += "\nType Mismatch:\n"
for exp, db in discrepancies['type_mismatch']:
report += f"Expected: {exp}, Database: {db}\n"
print(report)
# 关闭数据库连接
conn.close()
- 数据类型核对:在进行数据类型核对时,使用 eval(db_item[4]) 来将字符串转换为数据类型。这是一个简化的示例,实际应用中可能需要更安全的类型转换机制,因为直接使用 eval 可能存在安全风险。
- 报告生成:脚本生成的报告以文本形式输出,你可以根据需要将其保存到文件或发送给相关人员。
- 性能考量:对于大型数据集,使用双重循环进行数据比较可能不是最优的解决方案。在生产环境中,你可能需要使用更高效的算法或数据库查询来优化数据核对过程。
文章原创首发于微信公众号 软件测试微课堂