Проведение A/B-тестирования элементов страницы сайта
A/B-тестирование — контролируемый эксперимент, в котором одна группа пользователей видит оригинальную версию (контроль), другая — изменённую (вариант). Статистически значимая разница в конверсии — доказательство эффекта изменения.
Что можно тестировать
- Заголовок и текст CTA-кнопки
- Цвет, размер, расположение кнопки
- Форма: количество полей, порядок, лейблы
- Изображения и видео на главном экране
- Социальные доказательства (отзывы, счётчики)
- Элементы доверия (значки безопасности, гарантии)
- Цены и их отображение
Минимальный размер выборки
Перед запуском теста рассчитать необходимую выборку:
from scipy import stats
import math
def calculate_sample_size(baseline_rate, min_detectable_effect, alpha=0.05, power=0.8):
"""
baseline_rate: текущая конверсия (0.05 = 5%)
min_detectable_effect: минимальный эффект (0.10 = 10% относительное улучшение)
"""
p1 = baseline_rate
p2 = baseline_rate * (1 + min_detectable_effect)
z_alpha = stats.norm.ppf(1 - alpha / 2)
z_beta = stats.norm.ppf(power)
p_avg = (p1 + p2) / 2
n = (z_alpha * math.sqrt(2 * p_avg * (1 - p_avg)) +
z_beta * math.sqrt(p1 * (1 - p1) + p2 * (1 - p2))) ** 2 / (p2 - p1) ** 2
return math.ceil(n)
# Пример: конверсия 3%, хотим увидеть улучшение 15%+
n = calculate_sample_size(0.03, 0.15)
print(f"Need {n} users per variant = {n*2} total")
# Need 3,842 users per variant = 7,684 total
Реализация клиентского A/B теста
// Простой A/B тест без внешних инструментов
function getVariant(testName) {
const stored = localStorage.getItem(`ab_${testName}`)
if (stored) return stored
const variant = Math.random() < 0.5 ? 'control' : 'variant'
localStorage.setItem(`ab_${testName}`, variant)
// Отправить в аналитику
gtag('event', 'ab_test_assignment', {
test_name: testName,
variant: variant
})
return variant
}
// Применение теста
const variant = getVariant('cta_button_color')
const ctaButton = document.getElementById('main-cta')
if (variant === 'variant') {
ctaButton.style.backgroundColor = '#FF6B35' // оранжевый вместо синего
ctaButton.textContent = 'Начать бесплатно'
} else {
// Оставить по умолчанию
}
// Отслеживание конверсии
ctaButton.addEventListener('click', () => {
gtag('event', 'cta_click', {
test_name: 'cta_button_color',
variant: variant
})
})
Серверный A/B тест (предпочтительно)
Клиентский тест вызывает "мерцание" (flicker effect). Серверный — лучше:
// Laravel Middleware: назначить вариант до рендеринга
class AbTestMiddleware
{
public function handle(Request $request, Closure $next)
{
$testName = 'checkout_form_v2';
$userId = auth()->id() ?? $request->session()->getId();
// Детерминированное назначение по user ID
$variant = (crc32($userId . $testName) % 2 === 0) ? 'control' : 'variant';
$request->merge(['ab_variants' => [$testName => $variant]]);
View::share('ab_variants', [$testName => $variant]);
$response = $next($request);
$response->headers->set('X-AB-Variant', $variant);
return $response;
}
}
{{-- В шаблоне --}}
@if($ab_variants['checkout_form_v2'] === 'variant')
@include('checkout.form-v2')
@else
@include('checkout.form-v1')
@endif
Анализ результатов
from scipy.stats import chi2_contingency, proportions_ztest
import numpy as np
def analyze_ab_test(control_visitors, control_conversions,
variant_visitors, variant_conversions):
# Конверсии
cr_control = control_conversions / control_visitors
cr_variant = variant_conversions / variant_visitors
relative_change = (cr_variant - cr_control) / cr_control * 100
# Z-тест для пропорций
count = np.array([variant_conversions, control_conversions])
nobs = np.array([variant_visitors, control_visitors])
z_stat, p_value = proportions_ztest(count, nobs)
print(f"Control: {cr_control:.2%} ({control_conversions}/{control_visitors})")
print(f"Variant: {cr_variant:.2%} ({variant_conversions}/{variant_visitors})")
print(f"Relative change: {relative_change:+.1f}%")
print(f"P-value: {p_value:.4f}")
print(f"Statistically significant: {'YES' if p_value < 0.05 else 'NO'}")
analyze_ab_test(
control_visitors=3842, control_conversions=115,
variant_visitors=3891, variant_conversions=148
)
Распространённые ошибки
- Остановить тест раньше времени — p-value скачет в начале, нужно дожидаться расчётного объёма
- Тестировать несколько изменений сразу — непонятно, что дало эффект (это уже Multivariate)
- Игнорировать сегменты — тест нейтрален в целом, но улучшает мобильных на 25%
- Не учитывать сезонность — тестировать в репрезентативный период
Срок выполнения
Настройка A/B теста с серверным сплитом, аналитикой и анализом результатов — 2–4 рабочих дня.







