Розроблення модуля A/B-тестування для 1С-Bitrix
A/B-тест — це не просто «показати половині користувачів червону кнопку». Це правильне розділення аудиторії, стійке призначення варіанта (один користувач завжди бачить один варіант), відстеження цільових подій, статистична значимість результатів. Без модуля розробники зазвичай роблять це через if (rand(0,1)) — і отримують мерехтливі варіанти, марну статистику та неможливість масштабувати експерименти. Правильний модуль вирішує всі ці проблеми.
Модель даних
Модуль vendor.abtest:
-
b_vendor_ab_experiment— експерименти: id, name, code, status (draft/running/paused/finished), traffic_percent (10-100), started_at, finished_at, goal_event, description -
b_vendor_ab_variant— варіанти: id, experiment_id, name, weight (для нерівномірного розподілу), is_control -
b_vendor_ab_assignment— призначення: id, experiment_id, variant_id, user_id (або null), session_id, created_at -
b_vendor_ab_event— цільові подіï: id, experiment_id, variant_id, assignment_id, event_type, created_at -
b_vendor_ab_stat— агрегована статистика (денні зрізи): experiment_id, variant_id, date, participants, conversions, revenue
Стійке призначення варіанта
Користувач має завжди бачити один і той самий варіант. Призначення виконується один раз і зберігається:
class AssignmentService
{
public function getVariant(string $experimentCode): ?Variant
{
$experiment = ExperimentTable::getByCode($experimentCode);
if (!$experiment || $experiment['STATUS'] !== 'running') return null;
$sessionId = $this->getSessionId();
$userId = $GLOBALS['USER']->GetID() ?: null;
// Шукаємо існуюче призначення
$existing = AssignmentTable::getList([
'filter' => [
'EXPERIMENT_ID' => $experiment['ID'],
'LOGIC' => 'OR',
'USER_ID' => $userId,
'SESSION_ID' => $sessionId,
],
])->fetch();
if ($existing) {
return VariantTable::getById($existing['VARIANT_ID'])->fetch();
}
// Перевіряємо попадання у відсоток трафіку (детерміновано за session_id)
$hash = crc32($sessionId . $experimentCode) % 100;
if ($hash >= $experiment['TRAFFIC_PERCENT']) return null; // користувач поза тестом
// Вибираємо варіант за вагами
$variant = $this->selectVariant($experiment['ID']);
AssignmentTable::add([
'EXPERIMENT_ID' => $experiment['ID'],
'VARIANT_ID' => $variant['ID'],
'USER_ID' => $userId,
'SESSION_ID' => $sessionId,
]);
return $variant;
}
}
Детермінований хеш за session_id + experiment_code гарантує стійке попадання у відсоток трафіку без зберігання додаткових даних.
Застосування в шаблонах
У шаблоні компонента або лейауті:
$abTest = \Vendor\AbTest\AbTestService::getInstance();
$variant = $abTest->getVariant('checkout_button_color');
$buttonClass = match ($variant?->getName()) {
'green' => 'btn-success',
'orange' => 'btn-warning',
default => 'btn-primary', // контрольний варіант або поза тестом
};
У JavaScript:
// Дані варіанта передаються через data-атрибут або JS-змінну
const variant = window.ABTEST_VARIANTS?.checkout_button_color;
if (variant === 'new_form') {
document.querySelector('.checkout-form').classList.add('new-form');
}
Відстеження цільових подій
Відстежуємо конверсію — покупку, реєстрацію, клік на CTA:
// При оплаті замовлення
AddEventHandler('sale', 'OnSaleOrderPaid', function(\Bitrix\Main\Event $event) {
$orderId = $event->getParameter('id');
$order = \Bitrix\Sale\Order::load($orderId);
\Vendor\AbTest\EventTracker::track('purchase', [
'user_id' => $order->getUserId(),
'revenue' => $order->getPrice(),
'order_id' => $orderId,
]);
});
EventTracker::track() шукає активні призначення для даного користувача та записує подію у b_vendor_ab_event.
Статистична значимість
В адміністративному інтерфейсі для кожного експерименту розраховується:
- Конверсія за варіантами (conversion rate = events / participants)
- p-value за критерієм хі-квадрат (χ²) — поріг значимості 95%
- Uplift — відносне поліпшення порівняно з контрольним варіантом
- Sample ratio mismatch — перевірка, чи не порушилося співвідношення варіантів
// χ² тест для двох варіантів
function chiSquaredTest(int $a_visitors, int $a_conversions, int $b_visitors, int $b_conversions): float
{
$a_rate = $a_conversions / $a_visitors;
$b_rate = $b_conversions / $b_visitors;
$pooled = ($a_conversions + $b_conversions) / ($a_visitors + $b_visitors);
$expected_a = $pooled * $a_visitors;
$expected_b = $pooled * $b_visitors;
return (($a_conversions - $expected_a) ** 2 / $expected_a) +
(($b_conversions - $expected_b) ** 2 / $expected_b);
}
p-value < 0.05 → результат статистично значимий.
Терміни розроблення
| Етап | Тривалість |
|---|---|
| ORM-таблиці, детермінована призначення | 2 дні |
| Відстеження подій, інтеграція з продажами | 2 дні |
| Статистика, χ² тест | 2 дні |
| JavaScript SDK для фронтенда | 1 день |
| Адміністративний інтерфейс | 2 дні |
| Тестування | 1 день |
Разом: 10 робочих днів. Багатоваріантне тестування (3+ варіанти) та серверний сервіс feature-flag — додаткова оцінка.







