A/B测试:实现方法

上文介绍了 A/B 测试的基本概念,接下来我们继续探讨如何实现A/B测试。

我们先来看一个图:

(注:感谢Algo提供本图。)

上图展示了A/B测试的实现原理。从左到右,四条较粗的竖线代表了A/B测试中的四个关键角色:客户端(Client)、服务器(Server)、数据层(Data)、数据仓库(Data-Warehouse)。从上到下代表了三种访问形式:无A/B测试的普通访问流程(No-AB-test)、基于后端的 A/B 测试访问流程(Back-end AB test)、基于前端的 A/B 测试访问流程(Front-end AB test)。

一般情况下,用户在一次浏览中,会从客户端(Client)发起一个请求,这个请求被传到了服务器(Server),服务器的后台程序根据计算,得出要给用户返回什么内容(Data),同时向数据仓库(Data Warehouse)添加一条打点信息,记录本次访问的相关信息。这个过程也就是图上横向的流程。数据仓库收集到足够的数据之后,就可以开始进行分析(Analytics)了,这也即是图中右上角的部分。

A/B 测试需要将多个不同的版本展现给不同的用户,即需要一个“分流”的环节。从上图中我们可以看到,分流可以在客户端做,也可以在服务器端做。传统的 A/B 测试一般是在服务端分流的,即基于后端的 A/B 测试(Back-end AB test),当用户的请求到达服务器时,服务器根据一定的规则,给不同的用户返回不同的版本,同时记录数据的工作也在服务端完成。

基于后端的 A/B 测试技术实现上稍微简单一些,不过缺点是需要技术部工程资源介入,另外收集到的数据通常是比较宏观的PV(Page View)信息,虽然可以进行比较复杂的宏观行为分析,但要想知道用户在某个版本的页面上的具体行为往往就无能为力了。

基于前端的 A/B 测试则可以解决上面的问题。它的特点是,利用前端 JavaScript 方法,在客户端进行分流,同时,可以用 JavaScript 记录下用户的鼠标行为(甚至键盘行为,如果需要的话),直接发送到对应的打点服务器记录。这样的好处是不需要技术部(如果你们和我们一样,前端工程师与后端工程师分属不同部门的话)参与,并且可以比较精确地记录下用户在页面上的每一个行为,甚至包括后端方法难以记录到的无效点击!

下面,我将重点介绍一下我们在基于前端的 A/B 测试上的一些实践。

一、分流

首先遇到的问题是如何分流的问题。对于大部分需求来说,我们希望各个版本的访问人数平均分配。解决办法有很多种,比较简单的一种即是前面提到过的,根据某一个 Cookie ID 来划分用户,前提是你的网站上每一位访客在第一次访问时就要有一个不重复的 Cookie ID,比如“123.180.140.*.1267882109577.3”。然后,可以根据这个 Cookie ID 的最后一位(在本例中是“3”)来划分人群,比如单数的显示 A 版本,偶数的显示 B 版本。

因为 Cookie ID 一般设定后不会轻易改变,基于 Cookie ID 的好处是我们能很好地对访客保持一致性,某个用户如果第一次看到的是 A 版本,那他刷新后看到的还是 A 版本,不会一会儿看到 A 版本一会儿看到 B 版本。但不足之处就是如果用户浏览器不支持 Cookie 的话,分流就不能正常进行了。不过,现代浏览器默认情况下都是支持 Cookie 的,如果真有用户的浏览器不支持 Cookie ,那也应该是极少数特殊情况,对结果的影响非常微小,对于这些特殊情况,我们一般可以安全地忽略掉。

还有一点需要注意的是,A/B 测试的页面必须有较高的 UV (Unique Visitor,独立访客数),因为分流带有一定的随机性,如果页面 UV 太小,分到每一个版本的人数就更少,结果很有可能被一些偶然因素影响。而 UV 较大时,根据大数定理,我们得到的结果会接近于真实数据。就像想知道一个地方的成年人的平均身高,当然是取的样本越大结论越可信。

二、展示

决定向当前访问者显示哪个版本后,怎么用前端的方法加载对应的版本呢?这需要分情况处理。

一般情况下,如果两个版本只有一个较小的区域不一样,我们可以同时将两个区域的 HTML 都加载到当前页面中,先用 CSS 把它们隐藏起来(也可以默认显示一个版本),等 JS 判断出该显示哪个版本后,再控制对应版本的 CSS 显示。

有时候,测试区域比较大,代码比较多,或者需要后台较多的计算资源,如果一开始就把两个版本的 HTML 全加载到当前页面中,就会需要比较大的开销(比如带宽、后台计算量)。这种情况下,我们可以先把测试区留空,之后再用 Ajax 的方式延迟加载。

还有的时候,测试区域非常大,几乎占了整个页面,或者完全就是不同的页面,这时,用 Ajax 方式加载也不适合了,可以将不同的版本做成不同的页面,然后再用 JS 跳转。不过这样的方式并不是很好,因为前端 JS 跳转需要一定的时间,这个过程很有可能被用户感受到,并且留下不好的体验。对这个问题,似乎没有很好的解决办法,至少在前端层面很难完美解决,所以并不是非常推荐这种跳转方式,如果真的需要跳转,最好是在服务器端由后端代码来操作。

三、数据采集

正确展示对应的版本后,就要开始采集需要的数据了。有一个可选的数据,是当前版本有多少 PV (Page Views,访问量),如果需要记录这个数据的话,在正确版本加载完成之时就要发送一个打点信息。不过很多需求中,具体版本的 PV 的精确数值可能不是很重要,而且要收集这个信息需要多一次打点操作,所以一般情况下这个数据是可选的。

必须的数据是测试区域内用户的点击信息。当用户在测试区域点击了鼠标左键(无论这个点击是点击在链接、文字、图片还是空白处),我们就需要发送一条对应的打点信息到打点服务器。一般来说,这个打点信息至少需要包含以下数据:

当前 A/B 测试以及版本标识

点击事件的位置

点击时间戳(客户端时间)

当前点中的URL(如果点在非超链接区域,此项为空)

用户标识(比如 Cookie ID)

用户浏览器信息

为了尽可能精确地还原用户的点击位置,我们的页面对前端有比较高的要求,要求页面在不同的浏览器下有基本一致的表现,至少在IE6、7、8以及 Fiefox 下,页面横向的元素要精确一致,纵向上很难做到完全一致,但也要尽可能保持统一。另外,这样的测试也不太适合自适应宽度的页面,比较适合定宽的页面,为了避免不同分辨率下页面左右空白不同导致鼠标点击位置的不同,点击位置取的应该是相对于测试区域左上角的位置。除此之外,最好再记录一下测试区域相对于页面内容左上角的位置,在后面还原点击分布图以及绘制热区图时会用到这个数据。

这一阶段的流程大致如下图所示:

数据打点该如何发送以及如何存储呢?这要取决于你的打点服务器如何存储信息。

四、数据存储

我们使用了一台专用的服务器收集打点信息,为了能支持尽可多尽可能密集的打点请求,这台服务器的 apache 服务网站目录下只有两个静态文件,分别是 abtest.html 和 abtest.gif ,两者都是非常小的空白文件(空白图片)。访客端进行打点时,只需要以 GET 的方式带上相关的参数请求两个文件中的任意一个即可。比如:

http://abtest.xxx.com/abtest.gif?abid=1-a&clickBlockX=244&clickBlockY=372&clickBlockW=392&clickBlockH=76&clickTime=

1263264082137&clickRX=233&clickRY=47&clickURL=&clickBeaconID=
123.180.140.*.1267882109577.3&browserType=FireFox

这个请求可以通过 Ajax 的方式发送,也可以通过 JS 在页面上创建 new Image() 对象的方式完成。

对打点服务器来说,这只是一条普通的 HTTP 请求,它会在日志里留下一条普通的日志记录,形如:

123.180.140.* – – [13/Jan/2010:15:21:15 +0800] “GET /abtest.gif?a=123&b=456&c=789 HTTP/1.1″ 304 – “-” “Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.6 (KHTML, like Gecko) Chrome/4.0.266.0 Safari/532.6″

可以看到了,除了 JS 发送给我们的信息外,Apache 还帮我们记录了一些信息,比如访客 IP 、服务器时间、用户浏览器信息。

对于数据记录和存储来说,到这一步就足够了。Apache 静态文件 + 日志的方式足够高效,基本不用担心性能的问题。剩下的,就是另外一个问题,如何从 Apache 日志中读取打点信息并加以分析,这已经和前端无关了,并且是一个比较复杂的问题,将在后续日志中介绍。

源地址:http://www.aliued.cn/?p=2976

时间: 2024-10-21 10:24:04

A/B测试:实现方法的相关文章

junit 如何测试一个方法中的每行代码是否都被执行过?

问题描述 junit 如何测试一个方法中的每行代码是否都被执行过? 如题 测试一个方法中的每行代码是否都被执行过?是否有多余的代码? 解决方案 要知道一个代码块里面的代码是否被执行过,其实可以在该代码块最后加个输出语句即可,如果输出语句有执行,那么 前面的代码必然有执行. 但执行效果不一定是你的预期效果罢了(因为代码不一定是符合你的设计需求的) 解决方案二: 是否有多余的代码? 这个应该有代码校验工具. 解决方案三: 你这说的是白盒测试中的覆盖率测试,如果代码中有分支语句,需要编写多个测试用例才

关于junit测试单个方法的问题,请高手帮助 谢谢!

问题描述 我有一个测试类,里面很多测试方法,我想测试其中的一个,用junit3在eclipse中怎么测试啊谢谢回答 解决方案 解决方案二:eclipse自带帮助:Javadevelopmentuserguide>GettingStarted>BasictutorialWritingandrunningJUnittests解决方案三:在outline视图中,右键点击你要测试的方法,选runas---junittest解决方案四:引用2楼sun201200204的回复: 在outline视图中,右

敏捷——测试先行方法介绍

是的,现在肯定有读者会这样说了:"我只在产品发品之前写测试."有些人可能会窃笑,对质量保证部门说三道四.还有一些人作为项目经理可能会添油加醋地说:"我们可不会浪费时间写测试代码:我们还得写真正的代码呢."那么,采用TDD到底是什么意思呢? TDD产生于敏捷开发运动,特别是极限编程(extreme programming,XP),而且TDD正是XP的一个核心原则.推崇TDD的人认为,不应该完成开发之后再写测试,这通常只是"马后炮",而应当在写代码之

Xcode 真机测试破解方法(转加修改)xcode 4.3 通过

  Xcode 真机测试破解方法(转加修改)xcode 4.3 通过  生成本机证书 应用程序->实用工具->钥匙串访问 菜单:钥匙串访问->证书助理->创建证书, 然后按以下图片顺序操作即可:1.Xcode 3.2.5免证书开发调试_打开创建证书菜单 2.Xcode 3.2.5免证书开发调试_输入证书名称iPhone Developer并选择覆盖默认值 3.Xcode 3.2.5免证书开发调试_确定继续 4.Xcode 3.2.5免证书开发调试_输入序列号有效期,有效期不要超过3

java-hibernate框架,测试update方法时报空指针

问题描述 hibernate框架,测试update方法时报空指针 上面提示33行,33行也就一个字段一个方法,两个都找得到的 解决方案 可能是传进去的参数 或是执行的HQL语句问题,没有把数据插入. 空指针的情况很多哦! 解决方案二: user可能是空,,查一下数据库,是不是id为1的那个被删除了

佳能1100D相机测试动态范围方法分享

给各位摄影爱好者们来详细的解析分享一下佳能1100D相机测试动态范围的方法. 方法分享: 动态范围表示在一张照片中可以再现出多大的明暗差.测试EOS 1100D具有多宽的动态范围,如果动态范围有一定广度并且能够根据具体场景进行扩展就最好了.这里使用的是具有10级明暗差的动态范围测试片. EOS 1100D的高光色调优先:禁用8级 以中间亮度为基准,高光部和暗部均再现出4级.一共再现出8级明暗差.高光部一直保持了平滑的渐变,直到引起高光溢出的亮度.这在EOS数码单反系列中也是数一数二的高性能了.

PHP接口并发测试的方法(推荐)_php实例

如下所示: header('Content-type:text/html; Charset=utf-8'); $uri = "输入你的url"; $data = array( 'test'=>1 ); $ch = curl_init(); // print_r($ch); curl_setopt($ch, CURLOPT_URL, $uri); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_HEADER,

《数据结构与抽象:Java语言描述(原书第4版)》一2.1.5 测试核心方法

2.1.5 测试核心方法 准备.既然我们已经定义了3个核心方法,就可以测试它们.但是BagInterface中的其他方法怎么办呢?因为ArrayBag(程序清单2-1中给出的)实现了BagInterface,所以Java语法检查程序将查看这个接口中声明的每个方法的定义.我们是否应该等到完成它们的定义后才开始测试?绝对不要!在你写方法时就进行测试会让你尽早发现逻辑错误.不过,不是写BagInterface中每个方法的完整实现,而是对可暂时忽略的方法给出它们的不完整定义.一个不完整定义的方法称为存根

PHP接口并发测试的方法(推荐)

如下所示: header('Content-type:text/html; Charset=utf-8'); $uri = "输入你的url"; $data = array( 'test'=>1 ); $ch = curl_init(); // print_r($ch); curl_setopt($ch, CURLOPT_URL, $uri); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_HEADER,

用户体验研究之测试用户方法

"这是什么啊,用不明白,体验太差了!"."在哪里登录?找不到啊!"."谁知道这是个按钮可以点啊,像个图片!"在产品体验中,我们经常会听到这样那样有关产品体验的声音.而主动并客观地去发现这些问题(可用性问题)的方法之一,就是我们今天要介绍的用户研究方法之一--用户测试法. 什么是用户测试?通俗地讲,用户测试就是通过给用户制定任务,在用户执行任务的过程中,发现产品设计的不足,并为产品优化提供依据的一种方法. 通常情况下,根据目的不同,用户测试可以是定