后端的简单A/B测试
tracks的Python项目详细描述
你有没有想过a/b测试后端的多个代码路径?考虑 以下是:管理层希望您降低服务器成本。你来吧 事实上,你真的不需要一个128 GB内存的32核野兽 你的椰子店-但是你能卖多低?让我们测试一下 椰子目录页影响转换!(首先,没有tracks。
response=render_coconut_catalog()test_value=random.random()iftest_value<(1/3):# control groupsleep_sec=0eliftest_value<(2/3):sleep_sec=0.5else:sleep_sec=2time.sleep(sleep_sec)cur.execute('INSERT INTO test_runs (user_id, variant) VALUES (%s, %s)',user_id,sleep_sec)returnresponse
现在,这有点管用,但已经有点乱了,很容易就能摆脱 如果您现在考虑,请举手:
- The thing you test is really, really simple
- You don’t lock a user’s version to make sure they always get the same test variant, making your data more mushy with each page load.
- You don’t exclude your most important users from testing to avoid hurting conversion among coconut addicts in your initial test run. (Of course you will need to include them later to make an informed decision.)
- You already have a few more ideas about things you want to A/B test. Maybe a hundred.
- You can add another 5 engineers to the project and they will probably each implement these tests slightly differently, and that’s no good.
输入tracks。首先,我们定义我们的变体:
classDelayTracks(tracks.SimpleTrackSet):@staticmethoddefshort_track():time.sleep(0.5)@staticmethoddeflong_track():time.sleep(2)
使用它:
response=render_coconut_catalog()withDelayTracks()astrack:track()cur.execute('INSERT INTO test_runs (user_id, variant) VALUES (%s, %s)',user_id,track.name)returnresponse
已经好一点了,但是如果我们看一下 上面的问题一个接一个。首先,我们可以轻松地使测试更加复杂:
classPricingTracks(tracks.SimpleTrackSet):@staticmethoddefexpensive_track(response):forcoconutinresponse['coconuts']:coconut['price']+=1@staticmethoddefcheap_track(response):forcoconutinresponse['coconuts']:coconut['price']-=1
没变得那么糟,是吗?让我们更进一步 变型:
classPricingTracks(tracks.ParamTrackSet):params=[{'price_delta':n}forninrange(-5,6)]add_control_track=False# {'price_delta': 0} is our control group@staticmethoddeftrack_name(price_delta):return'price_adjusted_by_{0}'.format(price_delta)@staticmethoddeftrack(response,price_delta):forcoconutinresponse['coconuts']:coconut['price']+=price_delta
大量的测试!让我们转到第二个要点。如何锁定 为用户服务的变体?只需更改用法,将用户密钥传递给曲目即可:
response=render_coconut_catalog()withDelayTracks(key=user_id)astrack:track()cur.execute('INSERT INTO test_runs (user_id, variant) VALUES (%s, %s)',user_id,track.name)returnresponse
密钥将被序列化为字符串,并将派生要使用的变量 从那根绳子上。当然,关键是什么;在大多数情况下,关键可能是 用户ID,但您可以使用用户的国家和 例如,项目id。(不知道为什么你会想要这个具体的例子, 但你明白了。)
所以,解决了这个问题,列出第3项,我们来了!如果我们担心 我们的顶级客户对我们在他们身上测试东西很生气?轻松点。
classDelayTracks(tracks.SimpleTrackSet):@propertydefis_eligible(self):returnnotself.context['user']['is_vip']# code of variants trimmedresponse=render_coconut_catalog()withDelayTracks(context={'user':user_dict})astrack:track()# will always be control for VIPs (even with `add_control_group = False`)iftrack.is_eligible:cur.execute('INSERT INTO test_runs (user_id, variant) VALUES (%s, %s)',(user_id,track.name))returnresponse
最后,让我们尝试同时运行延迟和定价测试!
tracksets=[DelayTracks,PricingTracks]response=render_coconut_catalog()withtracks.MultiTrackSet(tracksets,key=user_id)asmultitrack:multitrack()cur.execute('INSERT INTO test_runs (user_id, test, variant) VALUES (%s, %s)',(user_id,multitrack.trackset.name,multitrack.track.name),)returnresponse
因此,有了这个,tracks将收集用户符合条件的所有测试,并且 根据给定的密钥选择其中一个。我们也可以设置 DelayTracks.weight到1000,使其使用的可能性增加10倍 作为定价(默认权重为100.)
我们只有几行代码,不是吗?