与proctor a/b测试框架交互的django库
django-proctor的Python项目详细描述
django proctor
django proctor允许您使用来自django的proctor(a/b测试框架),使用proctor pipet,它将proctor公开为一个简单的rest api。
proctor允许您将用户放入随机分配的测试组中进行a/b测试。它还可用于功能切换和新功能的逐步展开。
通过检查bucket的名称,使用django模板中的proctor group assignments非常简单:
{%ifproc.buttoncolortst.group=='blue'%}<buttonclass="blue-btn"></button>{%else%}<buttonclass="grey-btn"></button>{%endif%}
您还可以在python代码中使用proctor组:
ifrequest.proc.newfeaturerollout.group=='active':foo()else:bar()
如果需要在cron作业或某些服务中使用proctor组而无需请求,则可以执行帐户测试:
fromproctor.identifyimportproc_by_accountidaccountid=999999ifproc_by_accountid(accountid).newfeaturerollout.group=="active":foo()else:bar()
配置
在使用django proctor之前,您需要设置proctor pipet。这是django proctor与之通信以获得测试组分配的rest api。
您还需要一个测试矩阵,它定义所有proctor测试及其当前分配。proctor webapp提供查看和修改测试矩阵的方法。
要求
要使用django proctor,只需安装pip:
$ pip install django-proctor
或者将其添加到requirements.txt
文件中(最好是当前版本)。
视图
有一组私有视图可用于测试和调试。启用这些视图需要一些额外的配置。
已安装的应用程序
将proctor添加到项目的已安装应用程序列表中。
INSTALLED_APPS+=(...proctor,...)
网址
将proctor url导入到项目的私人空间中。
urlpatterns=[...url(r'^private/',include('proctor.urls'))...]
显示测试矩阵
showtestmatrix视图允许您查看特定proctor_测试的整个测试矩阵。此视图只是筛选到测试中的测试矩阵的json版本。
使用上面的url模式示例,测试矩阵可以在http://<;your_project_root>;/private/proctor/show
和http://<;your_project_root>;/private/show test matrix
上找到。后者是为了向后兼容其他项目。
部队
Force Groups(强制组)视图允许您查看会话和标识的当前组分配,并强制您进入特定组进行任何测试。
注意:此模板确实附带了默认的基本模板,但可能会被忽略。要覆盖默认的基本模板,必须有一个要扩展的基本模板,并且该模板文件的名称应在proctor基础模板中设置。
使用上面的url模式示例,force groups页面可以在http://<;your_project_root>;/private/proctor/force
中间件
django proctor的大部分处理都是在中间件中完成的。这将在视图之前运行,并将proc
添加到request
对象,该对象允许您访问测试组分配。
您必须子类化proctor.middleware.baseproctormiddleware并重写多个函数,以向django proctor提供处理组分配所需的信息。
classMyProctorMiddleware(proctor.middleware.BaseProctorMiddleware):...
获取u标识符()
标识符是唯一标识站点用户的字符串。这些可以包括跟踪cookies和帐户ID。Proctor使用此信息在请求之间将用户保持在相同的测试组中。
这会将标识符源键(请参阅pipe配置)的dict返回到它们的值。
如果用户缺少某个标识符,不要将其包含在dict中。proctor将跳过使用该标识符的任何测试。但是,请确保始终至少返回一个标识符,如跟踪cookie。
必须覆盖此方法。
方法总是在以前的任何中间件之后运行。
defget_identifiers(self,request):ids={'USER':request.COOKIES.get('tracking')}ifrequest.user.is_authenticated():ids['acctid']=request.user.idreturnids
获取上下文()
上下文变量是关于在proctor测试规则中使用的用户的属性,用于根据表达式是否为true有选择地启用测试或更改测试的分配。这可用于仅在Firefox上运行测试,或者您可以为我们用户运行50%的测试,为其他用户运行10%的测试。
这会将上下文变量源键(请参阅pipe配置)的dict返回到它们的值,这些值在计算规则表达式之前由pipe转换为它们的最终类型。
如果不重写此方法,则django proctor不使用上下文变量。
如果pipe配置没有上下文变量的默认值,则它必须包含在每个api请求中。如果是这种情况,请确保上下文变量出现在此返回值中。
此方法总是在以前的任何中间件之后运行。
defget_context(self,request):return{"ua":request.META.get('HTTP_USER_AGENT',''),"loggedIn":request.user.is_authenticated(),"country":geo.country(request.get_host()),}
是特权的()
返回一个bool,指示是否允许请求使用prforcegroups
query参数强制自己进入proctor组。
如果不重写此方法,则返回false,这将有效地禁用强制组。
使用类似IP地址或管理帐户的内容来确定用户是否可以强制自己进入测试组。
此方法可以在不触及任何先前中间件的情况下运行。您必须假设在中间件中可能做过的任何事情,例如将用户
属性添加到请求
,都可能没有发生。否则,在诸如重定向之类的奇怪情况下可能会出现异常。
{%ifproc.buttoncolortst.group=='blue'%}<buttonclass="blue-btn"></button>{%else%}<buttonclass="grey-btn"></button>{%endif%}0
获取http()
返回requests.session
(或等效)的实例,在向API发出HTTP请求时将使用该实例。
如果不重写此方法,则返回none,这将导致api使用请求
模块。
设置.py
要使django proctor正常工作,必须在settings.py
中设置一些内容:
中间件类
将您创建的中间件添加到中间件类
。确保将它放在它所依赖的任何中间件之后,比如authenticationmiddleware
模板上下文处理器
将proctor.context_processors.proc
添加到template_context_processors
中。这使得proc
对象在所有django模板中都可用。
程序api根目录
将proctor api根设置为proctor pipet运行的url。
包括http://或https://。不要包含尾随斜杠。
如果您希望生产使用与登台服务器和开发人员计算机不同的测试矩阵,则可能需要根据环境使用不同的pipe实例。
{%ifproc.buttoncolortst.group=='blue'%}<buttonclass="blue-btn"></button>{%else%}<buttonclass="grey-btn"></button>{%endif%}1
普氏试验
proctor_tests
是Django项目打算使用的proctor测试的元组或列表。
在模板和代码中实现测试之前,请将测试添加到此元组中,并在删除测试的实现之后从此元组中删除测试。
这里列出的所有测试都保证存在于proc
对象中。
如果测试组的值为非负,则此处列出的测试也将位于str(proc)(用于记录测试组)。
{%ifproc.buttoncolortst.group=='blue'%}<buttonclass="blue-btn"></button>{%else%}<buttonclass="grey-btn"></button>{%endif%}2
如果只使用一个测试,请确保元组中包含逗号。否则,python将其解释为一个字符串。
{%ifproc.buttoncolortst.group=='blue'%}<buttonclass="blue-btn"></button>{%else%}<buttonclass="grey-btn"></button>{%endif%}3
Proctor_Base_模板
将proctor基础模板设置为项目中使用的基础html模板的名称。
{%ifproc.buttoncolortst.group=='blue'%}<buttonclass="blue-btn"></button>{%else%}<buttonclass="grey-btn"></button>{%endif%}4
proctor_cache_方法
您可以选择从proctor pipet rest api中分配django proctor缓存组。通常,每个到达django的http请求都会触发proctor中间件向pipe发出http请求。您可以使用缓存来避免这一额外成本,因为组分配通常保持不变。
如果缓存没有有用的值(比如当一个新用户访问您的站点时),那么django proctor会返回到向proctor pipet发出http请求。
如果proctor_cache_method丢失或没有,django proctor将不会执行任何缓存。
如果proctor_cache_method是'cache'
,django proctor使用django的缓存框架来缓存组分配。请参见proctor缓存名称
如果proctor_cache_method
是'session'
,django proctor会在request.session
dict中缓存组分配。如果所有http请求都得到或设置d,这是一个不错的选择。Jango的会话对象无论如何。
缓存失效
django proctor的缓存失效相当聪明,如果用户请求的某些属性(如标识符、上下文变量或prforcegroups
参数)发生更改,则不会使用缓存。如果您更改proctor_api_root
或proctor_tests
等设置,缓存也将被忽略。
这意味着,如果用户登录,或用户更改其useragent,或您向proctor_tests
添加新测试,则将跳过缓存值。您不必担心过时的值。
TOdo:解释解决无效缓存问题后的矩阵版本检测和缓存。当前的缓存实现无法在多个进程中正常工作。
Proctor缓存名称
只有当proctor_cache_method是'cache'
时,此设置才有意义。
proctor_cache_name
是django proctor将使用的缓存中缓存的名称。
如果proctor_cache_name
丢失或没有,django proctor将使用默认的缓存。
懒散的proctor
如果proctor_lazy
为true
,则proc
对象将延迟加载其组。proctor组分配仅在第一次访问proc
对象时从缓存或proctor pipet rest api中检索。
这意味着,对Django服务器的从不使用proc
对象的HTTP请求不会产生获得组分配的成本。
在测量性能时,请记住此选项可能会将缓存访问和rest api请求的计时移到意外的位置(例如在模板呈现期间)。
如果proctor_lazy
丢失或false
,则不会使用延迟加载。
用法
proctor中间件将一个proc
对象添加到request
,这允许您从任何视图轻松使用proctor组分配。
可以使用proc
上的点运算符访问组分配。proctor_tests
中列出的每个测试都保证作为属性存在于proc
{%ifproc.buttoncolortst.group=='blue'%}<buttonclass="blue-btn"></button>{%else%}<buttonclass="grey-btn"></button>{%endif%}5
每个组分配有三个属性:
- 组:指定的测试组名(str)
- 值:指定的bucket值(int)
- -1通常表示不活动,0通常表示控制。
- 有效载荷:指定的测试组有效载荷值
- 用于从Proctor而不是代码中更改测试特定值。
- 如果测试没有有效载荷,则为"无"。
- 有效负载类型可以是str、long、double或其中之一的列表。
如果proctor没有为测试赋值,则该测试是未赋值的。在这种情况下:group、value和payload都是none
{%ifproc.buttoncolortst.group=='blue'%}<buttonclass="blue-btn"></button>{%else%}<buttonclass="grey-btn"></button>{%endif%}6
如果不符合资格规则,如果如果测试位于proctor_test s
但不在测试矩阵中,或者如果django proctor无法连接到pipet(或返回http错误),并且默认情况下将所有分配设置为未分配,则测试类型没有匹配的标识符。
切换
您可以根据用户分配的测试组,使用Proctor组分配在站点上实现不同的行为。
假设我们在测试矩阵中有一个名为"algorithmtst"的测试,它有四个测试组:'inactive'
,'control'
,'bogo'
,和'quick'
:
{%ifproc.buttoncolortst.group=='blue'%}<buttonclass="blue-btn"></button>{%else%}<buttonclass="grey-btn"></button>{%endif%}7
通常,您的'control'
、'inactive'
和none
组将具有相同的行为,即在添加此测试或功能之前执行站点所做的任何操作。else分支可以方便地覆盖所有这些组。
确保您的分支始终覆盖组为none
的情况。这确保了如果您的proctor pipet实例由于某些错误配置而关闭或开始返回http错误,您的站点将简单地恢复到默认行为。
模板
如果您正确设置了template\u context\u处理器,也可以从django模板使用proctor
模板的上下文中有proc
对象,允许您根据proctor组切换行为:
{%ifproc.buttoncolortst.group=='blue'%}<buttonclass="blue-btn"></button>{%else%}<buttonclass="grey-btn"></button>{%endif%}8
{%ifproc.buttoncolortst.group=='blue'%}<buttonclass="blue-btn"></button>{%else%}<buttonclass="grey-btn"></button>{%endif%}9
有效载荷
有效负载允许您在测试矩阵中而不是在代码中指定特定于测试的值。这允许您尝试许多不同的变体,而无需触摸django,甚至无需重新部署应用程序。
下面是行动要求按钮文本的示例:
ifrequest.proc.newfeaturerollout.group=='active':foo()else:bar()0
记住,在许多情况下,有效载荷可以是none
,包括如果proctor pipet下降。包括adefault\u if none过滤器,以确保发生这种情况时的理性默认行为。
也可以从您的视图中使用有效载荷:
ifrequest.proc.newfeaturerollout.group=='active':foo()else:bar()1
有效载荷阵列
有效载荷
在未分配测试组时为无
,因此任何属性访问仍然是错误的。
在python代码中,在访问有效负载的属性之前,请确保选中none
。
ifrequest.proc.newfeaturerollout.group=='active':foo()else:bar()2
有关详细信息,请参见django模板文档中的如何处理无效变量。
javascript
如果要使用浏览器端javascript中的proctor测试组分配,则必须通过django的模板语言向javascript提供要使用的值。
一个简单的方法是在html模板的脚本标记中定义全局javascript变量,其中包含代码将使用的值:
ifrequest.proc.newfeaturerollout.group=='active':foo()else:bar()3
对于字符串,用引号将模板输出标记括起来。使用escapejs过滤器,以便将引号和尖括号等特殊字符正确地放入javascript中。
有些人将这样的脚本标记放在"js"这样的模板块中,以便这些特殊值与其他脚本包含的内容一起出现在一致的位置。
您可以在javascript静态文件中使用这些全局变量来实现测试:
ifrequest.proc.newfeaturerollout.group=='active':foo()else:bar()4
这只是从浏览器访问proctor测试组的一种方法。使用什么ER对您的项目最有意义。
另一种方法是通过将代码放在html中并将django模板标记与javascript代码混合,直接对javascript进行模板化。您甚至可以将.js文件模板化,而不是静态地提供它们。然而,这两种选择可能会很混乱,不是最佳实践。
如果您的应用程序足够复杂,您甚至可以考虑生成一个django视图,返回一些测试组或有效负载,并让javascript发出ajax请求来获取它们。
日志记录
要比较两个不同测试组之间的度量,除了要跟踪的任何度量之外,还可以记录每个请求分配的测试组。
django proctor提供了一个简单的逗号分隔的表示,用于记录用户所在的所有proctor测试组:
ifrequest.proc.newfeaturerollout.group=='active':foo()else:bar()5
此输出仅包括非负测试组,因为-1通常表示不应记录的非活动组。
proc
对象还有一个方法,可以在使用逗号连接之前获取测试组列表:
ifrequest.proc.newfeaturerollout.group=='active':foo()else:bar()6
压力组
要测试测试组行为的实现,特权用户可以将prforcegroups
查询参数附加到其站点的url中,以强制自己加入某些测试组:
ifrequest.proc.newfeaturerollout.group=='active':foo()else:bar()7
格式只是测试名称后跟bucket值(而不是名称),所有测试组用逗号分隔。
prforcegroups的值设置为会话cookie。在关闭浏览器之前,您的浏览器将强制进入这些组。您还可以设置一个空的prforcegroups来清除cookie:
ifrequest.proc.newfeaturerollout.group=='active':foo()else:bar()8
prforcegroups
中指定的测试和bucket值必须存在于proctor测试矩阵中。
使用其他python框架中的proctor
django proctor主要是为django设计的,因为这是我们(实际上是实验室团队)主要使用的框架。
但是,这些模块可以在其他python框架中使用,只需稍作修改:
api
,cache*
,组
,标识
,惰性
不幸的是,缓存的某些子类中混入了一些django。它导入django.core.cache,在子类中使用django,抽象缓存接口以request
作为参数(因为sessioncacher
需要它,但对于所有其他子类来说,它都可以安全地为none)。
此外,在查找帐户详细信息时,出于类似原因,请从django设置中标识.py导入。
如果这对您来说是一个重要的问题,请让我们将其分为两个包:一个用于python,另一个用于django,后者将前者作为依赖项。或者提供一个拆分软件包的解决方案。
在其他框架中实现proctor时,请使用middleware.py
查看我们如何为django实现它。我们通过子类化处理提供上下文变量和标识符。其他实现可以注册函数(通过decorators或其他方式)来提供这些细节。另外,请注意prforcegroups查询参数和cookie是如何处理的。
测试
此项目使用tox
执行测试。运行测试
在本地,将CD放入您的项目并运行
ifrequest.proc.newfeaturerollout.group=='active':foo()else:bar()9
在引擎盖下面,tox
正在运行
pytest
用于测试发现和执行。测试参数可以
通过在tox
命令后添加--
来传递到pytest
中。例如,您可以
使用以下方法隔离测试文件或测试方法:
fromproctor.identifyimportproc_by_accountidaccountid=999999ifproc_by_accountid(accountid).newfeaturerollout.group=="active":foo()else:bar()0
其中pytest
使用双冒号作为测试类/方法/函数分隔符
默认情况下,pytest
捕获输出,从而防止使用断点进行调试. 如果你需要的话
调试测试,您可以运行以下任一操作:
fromproctor.identifyimportproc_by_accountidaccountid=999999ifproc_by_accountid(accountid).newfeaturerollout.group=="active":foo()else:bar()1
然后,您可以通过在python代码中添加以下内容来向测试添加断点:
fromproctor.identifyimportproc_by_accountidaccountid=999999ifproc_by_accountid(accountid).newfeaturerollout.group=="active":foo()else:bar()2
另请参见
proctor webapp用于编辑测试矩阵。
Indedeng Proctor用户了解问题和评论。