===============================================================================================
搜索埋点背景
由于搜索埋点数据过于老化,导致统计方面的一些工作的不方便。经过产品评估后决定需要重构部分埋点字段的参数,所以搜索的iOS端和Android端开始了一段全部埋点的测试之路。
0225版本新架构yksearch背景
❎ 流量分散、老sdk中包含大量且复杂的版本控制逻辑、埋点由客户端下发,容错率和迭代效率较低
✅ 埋点改为服务端下发。两端保持一致,新增统计字段时不需要重新发版
面临的困难
❎ 每次测试任务量巨大,要求QA覆盖全部46个类型卡片,100+种状态
❎ 单靠人肉diff工作量大且效率低
❎ 埋点日志字段较多。曝光埋点每个日志多达300+key value
❎ 埋点测试为S2季度客户端重点需求,不允许出错
解决办法
✅ Mock数据,构造全部产品形态,避免节目下线、节目类型难以查找等情况,辅助测试埋点
✅ 部分QA手工测试,文本在线diff
✅ 结合埋点治理平台UT(usertrack),抓取埋点日志
✅ 接入PLATO,正则表达式判断部分key value
✅ 设计埋点日志数据对比脚本,提升测试效率
===============================================================================================
===============================================================================================
设计思想:
1.定义数据data(单个json,校验track_info外的字段在track_info都要存在)、data1_old(老版本上报的埋点)、
data2_new(新版本上报的埋点)
2.定义第一个类DataPathTag,老版本path1、新版本path2、同一个数据内path,以上埋点数据文件存放的绝对路径
3.定义第二个类DataUtils是工具类:将文件流放到内存中,BufferedReader按行读取,
3.1 分离内外数据 3.1.1 内层与外层的区别标识为{},{为开始,}为结束,将内层与外层数据生成2个字符串track_info_outer、
track_info_inner, 找到track_info_inner第一个"="的下标,截取字符串,可强转为json
3.1.2 外层判断是否包含}
3.2 内层、外层数据转换 3.2.1 track_info_inner的格式本来就为json,所以直接处理json即可, 截取track_info_inner字符串,
json格式{}里所有内容,从{开始截取,直到}的位置,最后一位为"," { "searchtab":"0", "pageName":"page_searchresults", "group_num":38, }
3.2.2 track_info_outer的格式为A=1 外层数据处理,字符串转数组,数组先以","分隔,再以"="分隔,数组再转json,
如"A=1,B=2,C=3"-->[A=1,B=2,C=3]-->{"A":"1","B":"2","C":"1"}, object_id=dbb0ecb3786549098484, object_title=一出好戏, srid=1,
4. 第三个类JSONdifferent是新老数据的比较。调用DataUtils类,内层、外层分开判断,先判断key,再判断value
老版本的所有key必须在新版本中存在,如果不存在打印key,value。如果存在判断value是否相同,不相同打印
5. 第四个类SingleCompare是单个版本上报的数据校验,判断track_info内是否包含track_info外所有的key
如果不存在,打印key,value。如果存在判断value是否相同,不相同打印
6. 第五个类NewArchitecture是新老架构埋点数据的对比
内层判断:
校验老版本内层层必须不存在{"newArch"};
校验新版本内层层必须存在{"newArch"};
外层判断:
校验老版本外层必须存在{"spm_new","scm_new"};
新版本外层必须存在{"spm","scm"};
新版本外层必须不存在{"spm_new","scm_new"};
且新版本spm scm的value与老版本的spm_new scm_new的value必须一致
1.value中存在两个==,取第一个
2.value中存在:的情况,如曝光埋点_KG卡片(即UGC大词)"tagvalue":"2:0;1:0",json解析的时候出错
1. 工具类DataUtilsSingle:单个json格式日志解析
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
public class DataUtilsSingle {
public JSONObject innerJSON;
public JSONObject outerJSON;
//读取path路径的txt文件
public void txt2String(String path){
//StringBuilder为拼接字符串,减少对象的创建
StringBuilder track_info_outer = new StringBuilder();
StringBuilder track_info_inner = new StringBuilder();
//文件读取必须得用try catch,文件按行读取
try{
File file = new File(path);
BufferedReader br = new BufferedReader(new FileReader(file));//构造一个BufferedReader类来读取文件
String s;
boolean isInner = false;
while((s = br.readLine())!=null){//使用readLine方法,一次读一行
String result = s.trim();//去空白,末尾或者开头。去空格
//内外层数据分离,用{}区分
if (result.contains("{")){
isInner = true;
}else if (result.contains("}")){
isInner = false;
//"A=1,B=2,C=3..."
track_info_inner.append(result);
}
boolean con = result.contains("}");
if (!con){
if (isInner){
track_info_inner.append(result);
}else {
track_info_outer.append(result);
}
}
}
br.close();
//track_info_inner中等号(=)第一次出现的位置
int firstIndex = track_info_inner.indexOf("=");
//截取inner字符串,json格式{}里所有内容,从{开始截取,直到}的位置,最后一位为","
String innerStr = track_info_inner.substring(firstIndex+1);
if (innerStr.endsWith(",")){
innerStr = innerStr.substring(0,innerStr.length()-1);
}
String[] outer = track_info_outer.toString().split(",");
innerJSON = JSON.parseObject(innerStr);
outerJSON = new JSONObject();
for (int i=0;i<outer.length;i++){
String[] array = outer[i].split("=");
outerJSON.put(array[0].trim(),array[1].trim());
}
}catch(Exception e){
e.printStackTrace();
}
}
}
2. 工具类DataUtilsMulti:多个json即jsonlist格式日志解析
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.List;
public class DataUtilsMulti {
private JSONObject innerJSON;
public List<JSONObject> innerList = new ArrayList<>();
public JSONObject outerJSON;
//读取文件data.txt内容
public void txt2String(String path){
File file = new File(path);
StringBuilder track_info_outer = new StringBuilder();
StringBuilder track_info_inner = new StringBuilder();
try{
BufferedReader br = new BufferedReader(new FileReader(file));//构造一个BufferedReader类来读取文件
String s = null;
boolean isInner = false;
while((s = br.readLine())!=null){//使用readLine方法,一次读一行
String result = s.trim();
if (result.contains("{")){
isInner = true;
}else if (result.contains("}") && !result.contains("{")){
isInner = false;
track_info_inner.append(result);
continue;
}
if (isInner){
track_info_inner.append(result);
}else {
track_info_outer.append(result);
}
}
br.close();
//0311新增代码44 45 59,根据新架构pv日志格式调整,判断无track_info字段或者track_info为空的情况
String info_inner = track_info_inner.toString();
if (info_inner != null && info_inner.trim().length()!= 0) {
//track_info_inner中等号(=)第一次出现的位置
int firstIndex = track_info_inner.indexOf("=");
//从=算index,index+1为{。取出的字符串为{};{};{};{},
String inner = track_info_inner.substring(firstIndex + 1);
String innerStr = inner.substring(0, inner.length() - 1);
String[] innerArray = innerStr.split(";");
for (int i = 0; i < innerArray.length; i++) {
if (innerArray[i].trim().length() != 0) {
//{} {} {}
innerJSON = JSON.parseObject(innerArray[i]);
innerList.add(innerJSON);
}
}
}
String[] outer = track_info_outer.toString().split(",");
outerJSON = new JSONObject();
for (int i=0;i<outer.length;i++){
int index = outer[i].indexOf("=");
String[] array = outer[i].split("=",index+1);
outerJSON.put(array[0].trim(),array[1].trim());
}
}catch(Exception e){
e.printStackTrace();
}
}
}
3. 单个文件对比SingleCompare:对比单个json埋点日志track_info外层的key必须在内层存在,且值相等。判断内外层必须存在的MustExistKey的校验
新老版本对比:
新老架构对比:
点击埋点:
scm_new=20140669.search.rnovel.novel_shuqiyk://openapp?params=%7B%22pageName%22%3A%22bookDetail%22%2C%22params%22%3A%7B%22bookId%22%3A%227507742%22%7D%7D&back=1&from=s_7507742_c_7507742,
object_num=1,
pid=64b6847e992c4c45,
cate_id=109,
search_from=1,
searchtab=0,
track_info={
"source_from":"home",
"cate_id":109,
"group_num":1,
"engine":"expid~req.ugc119.sort1004.rank119.qa1$eid~0b8b380b15474577070643133ed057$bts~soku_qp#1@soku_engine_master#119@soku_irank#204@soku_ogc#301@soku_ugc#403@soku_ai#B@show_filter#B@image_search#A@search_discover#B@soku_resultpage_lessshow#B@soku_resultpage_newcard_58#B$r_p_n~31",
"aaid":"1e72ac097f6cbf359f7876122e999d0c",
"k":"天坑鹰猎",
"object_num":1,
"searchtab":"0",
"search_from":1,
"click_id":"1e72ac097f6cbf359f7876122e999d0c1547458217234",
"object_title":"天坑鹰猎(平装版)",
"object_id":"shuqiyk:\/\/openapp?params=%7B%22pageName%22%3A%22bookDetail%22%2C%22params%22%3A%7B%22bookId%22%3A%227507742%22%7D%7D&back=1&from=s_7507742_c_7507742",
"srid":1,
"view_type":1032
},
object_id=shuqiyk://openapp?params=%7B%22pageName%22%3A%22bookDetail%22%2C%22params%22%3A%7B%22bookId%22%3A%227507742%22%7D%7D&back=1&from=s_7507742_c_7507742,
object_title=天坑鹰猎(平装版),
srid=1,
group_num=1,
spm=a2h0c.8166622.rnovel.screenshot,
k=天坑鹰猎,
spm_new=a2h0c.8166622.PhoneSokuPromote_1.screenshot,
source_from=home,
scm=20140669.search.rnovel.novel_shuqiyk://openapp?params=%7B%22pageName%22%3A%22bookDetail%22%2C%22params%22%3A%7B%22bookId%22%3A%227507742%22%7D%7D&back=1&from=s_7507742_c_7507742,
aaid=1e72ac097f6cbf359f7876122e999d0c,
engine=expid~req.ugc119.sort1004.rank119.qa1$eid~0b8b380b15474577070643133ed057$bts~soku_qp#1@soku_engine_master#119@soku_irank#204@soku_ogc#301@soku_ugc#403@soku_ai#B@show_filter#B@image_search#A@search_discover#B@soku_resultpage_lessshow#B@soku_resultpage_newcard_58#B$r_p_n~31,
view_type=1032
曝光埋点:
spm_new=a2h0c.8166622.PhoneSokuUgc_4.screenshot;a2h0c.8166622.PhoneSokuUgc_5.screenshot,
scm=20140669.search.rugc.video_XMzgzODM5ODM2NA==;20140669.search.rugc.video_XMzg1OTM0MDEzNg==,
source_from=home,
spm=a2h0c.8166622.rugc.screenshot;a2h0c.8166622.rugc.screenshot,
scm_new=20140669.search.rugc.video_XMzgzODM5ODM2NA==;20140669.search.rugc.video_XMzg1OTM0MDEzNg==,
track_info={
"srid":1,
"item_log":"doc_source~2$eng_source~6$sp_id~3458764514780140519",
"source_from":"home",
"group_num":4,
"engine":"expid~req.ugc119.sort1004.rank119.qa1$eid~0b8b380b15474587002515969ed05c$bts~soku_qp#1@soku_engine_master#119@soku_irank#204@soku_ogc#301@soku_ugc#403@soku_ai#B@show_filter#B@image_search#A@search_discover#B@soku_resultpage_lessshow#B@soku_resultpage_newcard_58#B$r_p_n~31",
"aaid":"c869247b685afb310e227b16a7943a1e",
"k":"天坑鹰猎",
"object_num":1,
"object_type":1,
"group_id":"XMzgzODM5ODM2NA==",
"isplay":11,
"searchtab":"0",
"object_id":"XMzgzODM5ODM2NA==",
"object_title":"《天坑鹰猎》王俊凯误认为小姑娘喜欢她,
呆萌的模样反被嘲笑",
"search_from":3,
"click_id":"c869247b685afb310e227b16a7943a1e1547458702726",
"view_type":1005,
"cate_id":-21
};{
"srid":1,
"item_log":"doc_source~2$eng_source~6$sp_id~3458764514785375962",
"source_from":"home",
"group_num":5,
"engine":"expid~req.ugc119.sort1004.rank119.qa1$eid~0b8b380b15474587002515969ed05c$bts~soku_qp#1@soku_engine_master#119@soku_irank#204@soku_ogc#301@soku_ugc#403@soku_ai#B@show_filter#B@image_search#A@search_discover#B@soku_resultpage_lessshow#B@soku_resultpage_newcard_58#B$r_p_n~31",
"aaid":"c869247b685afb310e227b16a7943a1e",
"k":"天坑鹰猎",
"object_num":1,
"object_type":1,
"group_id":"XMzg1OTM0MDEzNg==",
"isplay":11,
"searchtab":"0",
"object_id":"XMzg1OTM0MDEzNg==",
"object_title":"天坑鹰猎:大结局终于看透张保庆的心思了,最不舍得的还是菜瓜!",
"search_from":3,
"click_id":"c869247b685afb310e227b16a7943a1e1547458702727",
"view_type":1005,
"cate_id":-21
},
search_from=3,
engine=expid~req.ugc119.sort1004.rank119.qa1$eid~0b8b380b15474587002515969ed05c$bts~soku_qp#1@soku_engine_master#119@soku_irank#204@soku_ogc#301@soku_ugc#403@soku_ai#B@show_filter#B@image_search#A@search_discover#B@soku_resultpage_lessshow#B@soku_resultpage_newcard_58#B$r_p_n~31,
pid=64b6847e992c4c45