РЕАЛЬНОЕ СОБЕСЕДОВАНИЕ / Middle ТЕСТИРОВЩИК Bell Integrator - от 130 000
Сегодня мы разберем собеседование на позицию QA-инженера, где кандидат Яков прошел через серию практических задач по тест-дизайну, отладке веб-приложений и SQL-запросам, демонстрируя уверенные знания инструментов вроде Postman, Charles Proxy и DevTools. Интервьюеры, включая Сергея и Романа, активно взаимодействовали с кандидатом, уточняя ответы и предлагая сценарии реального тестирования, такие как проверка лимитов банкомата или анализ HTTP-ответов. Обсуждение завершилось вопросами о команде и условиях работы, подчеркнув фокус на удаленной занятости и профессиональном росте.
Вопрос 1. Как протестировать кофемашину, если основная функция - приготовить вкусный кофе, с кнопками для эспрессо, американо и капучино?
Таймкод: 00:01:37
Ответ собеседника: неполный. Проверить внешний вид на повреждения, провести смок-тест основных функций, протестировать приготовление разных видов кофе, проверить поведение при отключении электричества и прерывании процесса наливания.
Правильный ответ:
Тестирование кофемашины, где основная функция — приготовление вкусного кофе с использованием кнопок для эспрессо, американо и капучино, требует системного подхода, аналогичного тестированию сложной системы в софте. Это включает unit-тестирование компонентов (например, отдельных механизмов), интеграционное тестирование (взаимодействие частей) и end-to-end тестирование (полный цикл от нажатия кнопки до получения кофе). Я структурирую ответ по уровням тестирования, чтобы охватить функциональные, нефункциональные аспекты и edge-кейсы, с акцентом на reproducibility, автоматизацию и метрики качества. Это поможет выявить дефекты на ранних этапах и обеспечить надежность.
1. Подготовка к тестированию
Перед запуском тестов соберите требования: вкусный кофе подразумевает правильную температуру (85–95°C для эспрессо), объем (30 мл для эспрессо, 150–200 мл для американо, 150 мл с пенкой для капучино), время приготовления (25–30 сек для эспрессо) и отсутствие примесей. Определите инструменты: мультиметр для электрики, термометр, весы для объема, таймер. Для автоматизации в софте (если кофемашина имеет IoT-интерфейс) используйте фреймворки вроде Go's testing package или Selenium для UI.
Создайте тест-кейсы в таблице:
- Input: Нажатие кнопки (эспрессо/американо/капучино).
- Expected Output: Вкусный кофе по стандартам (сенсорная оценка или измерения).
- Pass/Fail Criteria: Объем ±5%, температура ±2°C, вкус без горечи (слепая дегустация).
2. Unit-тестирование (компоненты в изоляции)
Тестируйте отдельные части, чтобы убедиться, что они работают независимо. Аналогия в Go: используйте table-driven tests для проверки логики.
- Бойлер (нагреватель): Проверьте, достигает ли он 90°C за 1 минуту. Тест: Включить без кофе — измерить температуру. Edge-кейс: Перегрев >100°C (риск кипения).
- Помпа и клапаны: Для эспрессо — давление 9 бар, для американо — разбавление водой. Тест: Запустить без зерен, измерить поток (используйте манометр).
- Механизм помола: Для свежести — равномерный помол 7–10 г за 10 сек. Тест: Взвешивать выход.
- Паровой насос для капучино: Пена должна быть кремовой (объем удвоить). Тест: Взбить молоко, оценить микропену.
В Go-примере для симуляции (если это софт-модель кофемашины):
package main
import (
"testing"
"time"
)
func TestBoilerHeating(t *testing.T) {
tests := []struct {
name string
duration time.Duration
wantTemp float64
}{
{"Standard heat", 60 * time.Second, 90.0},
{"Overheat edge", 120 * time.Second, 95.0}, // Не >100
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Симуляция нагрева
temp := simulateHeating(tt.duration)
if temp > 100 {
t.Errorf("Temperature too high: got %f, want <=100", temp)
}
if math.Abs(temp-tt.wantTemp) > 2 {
t.Errorf("Temperature mismatch: got %f, want %f", temp, tt.wantTemp)
}
})
}
}
func simulateHeating(d time.Duration) float64 {
// Упрощенная модель: 1°C/сек
return float64(d.Seconds())
}
Это обеспечивает изоляцию и быстрый фидбек.
3. Интеграционное тестирование (взаимодействие компонентов)
Проверьте, как части работают вместе, фокусируясь на кнопках.
- Кнопки и контроллер: Нажать эспрессо — активировать помол + нагрев + помпу. Тест: Последовательность (логировать шаги). Ожидаемый: 25–30 сек, 30 мл при 90°C.
- Американо: Эспрессо + горячая вода. Тест: Общий объем 180 мл, вкус сбалансированный (не разбавленный).
- Капучино: Эспрессо + вспененное молоко. Тест: Соотношение 1:1:1 (кофе:молоко:пена), температура 65°C.
- Сенсорный тест вкуса: Используйте панель дегустаторов (3–5 человек) для субъективной оценки (шкала 1–10 по аромату, горечи). Автоматизируйте с камерой для визуальной пены.
Edge-кейсы:
- Прерывание: Нажать кнопку во время приготовления — машина должна остановиться gracefully (слив остатков, очистка).
- Отключение электричества: Восстановление состояния (resume или reset). Тест: Выдернуть вилку на 10 сек — проверить, не сгорел ли бойлер.
- Переполнение: Бак воды пуст/полон — ошибка без утечек.
4. End-to-End тестирование (полный пользовательский сценарий)
Симулируйте реальное использование: Загрузить зерна/воду/молоко, нажать кнопку, получить кофе.
- Smoke-тест: Базовый цикл для всех напитков — машина включается, готовит, выключается без ошибок.
- Регрессионное: После фиксов повторить все тесты.
- Load-тестирование: 50 циклов подряд — проверить износ (температура стабильна? Нет перегрева?).
- Нефункциональное:
- Производительность: Время <1 мин на напиток.
- Надежность: MTBF >1000 циклов (среднее время между сбоями).
- Безопасность: Нет ожогов (защита от касания), электрическая изоляция (тест на утечку тока).
- Доступность: Кнопки отзывчивы, индикаторы (LED) показывают статус.
Для автоматизации в софте (IoT-кофемашина с API):
package main
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestEspressoEndpoint(t *testing.T) {
// Мок-сервер для симуляции машины
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/brew/espresso" {
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"volume":30,"temp":90,"status":"ready"}`))
} else {
w.WriteHeader(http.StatusBadRequest)
}
}))
defer server.Close()
resp, err := http.Get(server.URL + "/brew/espresso")
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("Expected OK, got %d", resp.StatusCode)
}
// Парсинг и проверка JSON для вкуса/объема
}
Это тестирует API-интеграцию, где кнопка — это HTTP-запрос.
5. Документация и отчетность
Логируйте все: Видео/фото результатов, метрики (Jira или Excel). Покрытие тестов >80%. Если дефект — root cause analysis (5 Whys). Для вкуса: Стандартизируйте с SCA (Specialty Coffee Association) протоколами.
Этот подход минимизирует риски, обеспечивая, что кофемашина не только готовит кофе, но и делает это consistently и safely. В реальном проекте интегрируйте CI/CD для автоматизированных тестов.
Вопрос 2. Какие проверки провести для функционала банкомата по снятию наличных с карты, где на карте миллион рублей, суточный лимит 500 тысяч, разовый лимит 200 тысяч, идеальные условия без сбоев?
Таймкод: 00:04:32
Ответ собеседника: правильный. Разбить на классы эквивалентности: от 0 до 200 тысяч (позитив: 100, 199 тысяч; негатив: 1 рубль, если не выдает мелкие суммы; граница 200 тысяч позитив, 201 тысяча негатив), от 200 до 500 тысяч (позитив: 300 тысяч), свыше 500 тысяч негатив; учесть суточный лимит с проверкой в конце дня, минимальные купюры от 100 рублей.
Правильный ответ:
Тестирование функционала снятия наличных в банкомате подразумевает тщательный анализ границ и классов эквивалентности, чтобы обеспечить корректную обработку лимитов и баланса карты. С учетом идеальных условий (нет сетевых сбоев, карта валидна, банкомат полностью загружен купюрами), фокус на функциональном тестировании: проверка логики валидации сумм, обновления баланса и учета суточного лимита. Это включает equivalence partitioning (разделение входных данных на классы, где поведение одинаково) и boundary value analysis (тестирование границ классов). В реальном проекте такие тесты автоматизируют с использованием фреймворков вроде Go's testing или Cucumber для BDD, с интеграцией к БД для симуляции транзакций. Я разберу подход шаг за шагом, с примерами тест-кейсов и кода, чтобы подчеркнуть ключевые моменты: reproducibility, покрытие edge-кейсов и предотвращение перерасхода.
1. Подготовка к тестированию
Определите ключевые параметры:
- Баланс карты: 1 000 000 руб (достаточно для всех тестов).
- Разовый лимит: ≤ 200 000 руб на транзакцию.
- Суточный лимит: ≤ 500 000 руб за 24 часа (учитывать время: суточный счетчик сбрасывается в 00:00, проверять остаток на момент запроса).
- Минимальные/максимальные суммы: От 100 руб (учитывая номиналы купюр: 100, 500, 1000, 5000 руб; банкомат не выдает <100 или некратные номиналам).
- Валюта и точность: Рубли, целые числа (без копеек для простоты).
Инструменты:
- Эмулятор банкомата (API или UI-тестер вроде Postman/Selenium).
- БД для хранения баланса/лимита (SQL: таблицы accounts, daily_limits).
- Метрики успеха: Транзакция проходит (PIN верен, сумма списана, наличные выданы), баланс обновлен, лимит уменьшен. Fail — если сумма отклонена с правильным сообщением (e.g., "Превышен лимит").
Создайте тест-план в формате таблицы для traceability:
| Тест-кейс ID | Входная сумма (руб) | Класс эквивалентности | Ожидаемый результат | Boundary? |
|---|---|---|---|---|
| TC001 | 100 | Валидная (0-200k) | Успех: Выдать 100 руб (1x100) | Нижняя граница |
| TC002 | 199999 | Валидная (0-200k) | Успех: Выдать по номиналам | - |
| TC003 | 200000 | Валидная (0-200k) | Успех: Выдать 200k | Верхняя граница |
| TC004 | 200001 | Невалидная (>200k) | Отказ: "Превышен разовый лимит" | - |
| ... | ... | ... | ... | ... |
Цель — 100% покрытие классов, включая негативные (инвалидные суммы).
2. Классы эквивалентности и boundary testing
Разделим суммы на классы на основе лимитов и баланса. Поскольку баланс 1M > лимитов, он не ограничивает, но всегда проверяем списание ≤ баланса.
-
Класс 1: Валидные суммы в разовом лимите (0 < сумма ≤ 200 000 руб):
- Позитив: Суммы, кратные номиналам купюр (e.g., 100, 500, 1000 руб). Тест: Снять 150 000 руб — банкомат выдает (e.g., 30x5000), баланс -=150k, разовый лимит не меняется (он per transaction), суточный -=150k.
- Boundary: 100 руб (мин, если поддерживается), 200 000 руб (max). Тест: 200 000 — успех; 99 руб — отказ ("Минимальная сумма 100 руб").
- Важно: Проверить диспенсинг — сумма должна быть представима комбинацией купюр без остатка (e.g., 150 руб = 1x100 + 1x50? Если 50 нет, отказ или округление? Уточнить требования).
-
Класс 2: Невалидные суммы в разовом лимите (0 = сумма < 100 или >200 000, или некратная номиналам):
- Негатив: 0 руб — отказ ("Сумма не может быть нулевой"). 50 руб — отказ (если мин 100). 201 000 руб — отказ ("Превышен разовый лимит"). 150 050 руб — отказ, если не кратно (зависит от номиналов).
- Boundary: 200 001 руб (сразу после max). Тест: Ввести в UI/API — получить HTTP 400 или эквивалент с сообщением.
-
Класс 3: Суммы между разовым и суточным лимитом (200 001 < сумма ≤ 500 000 руб):
- Позитив для нескольких транзакций: Одна транзакция >200k невозможна, но последовательность (e.g., 200k + 100k = 300k в сутки) — успех, если остаток суточного позволяет. Тест: Снять 200k (успех), затем 100k (успех, суточный -=300k).
- Негатив: Попытка 300k за раз — отказ ("Превышен разовый лимит").
-
Класс 4: Превышение суточного лимита (>500 000 руб за сутки):
- Негатив: После снятия 500k в течение дня (e.g., 5x100k), следующая транзакция любой суммы — отказ ("Превышен суточный лимит"). Тест: Симулировать время — снять 400k в 10:00, 100k в 14:00 (успех), 1k в 18:00 (отказ).
- Boundary: Ровно 500 000 руб за сутки — успех для последней транзакции; 500 001 — отказ. Учесть сброс: Тест в 23:59 (лимит исчерпан), 00:01 (сброс, лимит 500k снова).
-
Дополнительные классы:
- Баланс: Хотя 1M, тест 900 001 руб — отказ ("Недостаточно средств"), но в идеале не актуально.
- Номиналы: Сумма, не представимая (e.g., 10 руб, если нет 10-рублевых) — отказ с "Невозможно выдать сумму".
Для суточного лимита: Хранить в БД с timestamp. Тест последовательности транзакций в одном сеансе и через время.
3. Тестирование последовательностей и edge-кейсов
- Множественные транзакции: Снять 200k, затем 200k, 100k (сумма 500k) — все успех; следующая 1 руб — отказ.
- Время и сброс: Используйте mock времени. Тест: Снять 500k, подождать "ночь" (симулировать), снять еще 200k — успех.
- PIN и аутентификация: Хотя идеальные условия, проверить, что после 3 неверных PIN — блокировка, но не для снятия.
- Пост-условия: После успеха — чек распечатан/отправлен SMS, логи в БД (баланс, лимит updated atomically).
Edge-кейсы в идеальных условиях:
- Сумма = балансу (1M, но >500k суточный — отказ по лимиту).
- Нулевой остаток суточного после частичного дня.
4. Автоматизация тестов
В Go реализуйте unit-тесты для бизнес-логики (валидация лимитов), интеграционные — с SQL для БД.
Пример Go-теста для валидации суммы (table-driven):
package atm
import (
"testing"
)
type TestCase struct {
name string
amount int
dailyLimit int
dailySpent int
balance int
singleLimit int
wantValid bool
wantErrorMsg string
}
func TestValidateWithdrawal(t *testing.T) {
tests := []TestCase{
{"Valid single", 150000, 500000, 0, 1000000, 200000, true, ""},
{"Boundary single max", 200000, 500000, 0, 1000000, 200000, true, ""},
{"Invalid single over", 200001, 500000, 0, 1000000, 200000, false, "Превышен разовый лимит"},
{"Valid daily partial", 100000, 500000, 400000, 1000000, 200000, true, ""},
{"Invalid daily over", 100000, 500000, 400001, 1000000, 200000, false, "Превышен суточный лимит"},
{"Min amount", 99, 500000, 0, 1000000, 200000, false, "Минимальная сумма 100 руб"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
valid, err := ValidateWithdrawal(tt.amount, tt.dailyLimit-tt.dailySpent, tt.balance, tt.singleLimit)
if valid != tt.wantValid {
t.Errorf("ValidateWithdrawal() valid = %v, want %v", valid, tt.wantValid)
}
if err != nil && err.Error() != tt.wantErrorMsg {
t.Errorf("ValidateWithdrawal() error = %v, want %v", err, tt.wantErrorMsg)
}
})
}
}
func ValidateWithdrawal(amount, dailyRemaining, balance, singleLimit int) (bool, error) {
if amount < 100 {
return false, errors.New("Минимальная сумма 100 руб")
}
if amount > singleLimit {
return false, errors.New("Превышен разовый лимит")
}
if amount > dailyRemaining || amount > balance {
return false, errors.New("Превышен суточный лимит или баланс")
}
// Здесь добавить проверку номиналов (e.g., amount % 100 == 0)
return true, nil
}
Для БД (SQL): Симулируйте транзакцию. Пример запроса для обновления:
-- Проверка и обновление баланса/лимита атомарно
BEGIN TRANSACTION;
SELECT balance, daily_spent FROM accounts WHERE card_id = '12345' FOR UPDATE;
-- В Go: парсить и валидировать
UPDATE accounts
SET balance = balance - 150000,
daily_spent = daily_spent + 150000
WHERE card_id = '12345' AND balance >= 150000 AND (daily_limit - daily_spent) >= 150000;
-- Если rows affected == 0, отказ
COMMIT;
Интеграционный тест в Go может использовать sqlmock для мокинга БД, проверяя, что UPDATE проходит только при валидных условиях.
5. Нефункциональные аспекты и завершение
Даже в идеальных условиях, проверьте производительность: Транзакция <5 сек (время диспенсинга). Логирование: Все отказы/успехи в audit-log. Покрытие: >95% для лимитной логики. После тестов — отчет с дефектами (если сумма не кратна — баг?).
Этот подход гарантирует robustность: банкомат не позволит превысить лимиты, минимизируя финансовые риски. В production добавьте мониторинг (Prometheus) и A/B-тестирование для UI.
Вопрос 3. В веб-приложении заполнили форму данными и нажали кнопку 'сохранить', но ничего не произошло. Какие действия предпринять для диагностики?
Таймкод: 00:12:12
Ответ собеседника: правильный. Проверить в DevTools, отправился ли HTTP-запрос. Если нет - ошибка на фронтенде. Если да - осмотреть код ответа (200 - успех, 300 - перенаправление, 400 - клиентская ошибка, 500 - серверная), заголовки и тело ответа на наличие ошибок на бэкенде или фронтенде.
Правильный ответ:
Диагностика сценария, когда форма в веб-приложении не реагирует на нажатие кнопки "Сохранить" (нет обновления UI, редиректа или сообщения об ошибке), требует систематического подхода: от клиентской стороны (фронтенд) к серверной (бэкенд), включая сеть, логи и инфраструктуру. Это классический случай silent failure, где ошибка может быть в event handling, валидации, сетевом вызове или обработке на сервере. В production-окружении используйте инструменты вроде Chrome DevTools, Wireshark для трафика или APM (New Relic, Datadog) для end-to-end visibility. Я опишу шаги последовательно, с акцентом на root cause analysis: всегда начинайте с клиента, чтобы изолировать проблему, и эскалируйте к серверу. Для Golang-бэкенда (типичного для API) добавлю примеры кода для типичного handler'а формы и SQL-взаимодействия, чтобы показать, где могут скрываться баги. Цель — быстро локализовать и воспроизвести issue, минимизируя downtime.
1. Проверка клиентской стороны (Фронтенд: UI и JavaScript)
Сначала убедитесь, что проблема не в браузере или локальной логике — 80% silent failures здесь.
-
Откройте DevTools (F12 в Chrome/Edge):
- Console tab: Ищите JavaScript ошибки (red text). Например,
Uncaught TypeError: Cannot read property 'addEventListener' of null— кнопка не найдена (селектор неверный, DOM не загружен). ИлиReferenceError: submitForm is undefined— функция не определена. Проверьте warnings о CORS, mixed content (HTTP/HTTPS mismatch). - Elements tab: Инспектируйте кнопку "Сохранить". Проверьте, не disabled ли она (
disabled="true") или не скрыта (display: none). Посмотрите на form:onsubmithandler привязан? Если это SPA (React/Vue), проверьте state (e.g., loading spinner не запущен?). - Sources tab: Установите breakpoint на event listener кнопки (e.g.,
button.addEventListener('click', handleSubmit)). Нажмите кнопку — выполняется ли код? Если нет, проблема в binding (e.g., script загружен после DOM).
- Console tab: Ищите JavaScript ошибки (red text). Например,
-
Event handling тест:
- В Console выполните вручную:
document.querySelector('button[type="submit"]').click()— отправляется ли запрос? Если нет, event.preventDefault() блокирует submit (часто для AJAX). - Если форма использует fetch/XMLHttpRequest: Проверьте, вызывается ли функция (console.log в начале handleSubmit).
- В Console выполните вручную:
Пример фронтенд-кода (JS), где может сломаться:
// Типичный AJAX-submit
function handleSubmit(event) {
event.preventDefault(); // Блокирует page reload
const formData = new FormData(document.getElementById('myForm'));
console.log('Submitting...'); // Лог для дебага
fetch('/api/save', {
method: 'POST',
body: formData
}).then(response => {
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
}).then(data => {
alert('Saved!'); // UI feedback
}).catch(error => {
console.error('Error:', error); // Silent fail, если нет catch
// Здесь может быть проблема: нет UI-уведомления
});
}
Если console.log не появляется — event не срабатывает. Фикс: Убедитесь, что script в <body> после формы или используйте DOMContentLoaded.
- Дополнительно: Очистите cache (Ctrl+Shift+R), проверьте в incognito (расширения не мешают). Тестируйте в другом браузере — issue в polyfill'ах (e.g., IE11 без fetch).
2. Проверка сетевого уровня (HTTP-запрос)
Если фронтенд чист, проблема в коммуникации.
-
Network tab в DevTools:
- Фильтр: "XHR/Fetch" или "/api/save". Нажмите кнопку — появился ли запрос? Если нет, фронтенд не инициировал (вернитесь к шагу 1).
- Если запрос есть:
- Status code: 200/201 — успех, но UI не обновляется (проблема в .then() или silent success). 204 — no content, нормально для POST без body. 3xx — redirect (следуйте за ним в Network). 4xx (400 Bad Request, 401 Unauthorized, 422 Unprocessable Entity) — клиентская ошибка (валидация данных, auth token expired). 5xx (500 Internal Server Error, 502/503) — серверная (crash в handler).
- Request/Response headers: Проверьте Content-Type (multipart/form-data для файлов?), Authorization (Bearer token?). Response: X-Error? JSON body с ошибкой (e.g., {"error": "Invalid email"}).
- Payload: Данные формы отправлены правильно? (e.g., missing field). Timing: Если >30s — timeout (CORS или firewall).
- Initiator: Кликните на запрос — увидите стек JS, где он стартовал.
-
Edge-кейсы: CORS error в Console ("Access-Control-Allow-Origin missing") — сервер не настроен. Network throttling (в DevTools > Network > Slow 3G) — симулируйте latency, чтобы выявить race conditions.
Если запрос не уходит: Проверьте URL (relative vs absolute), прокси/VPN.
3. Диагностика серверной стороны (Бэкенд: Golang API)
Если запрос дошел, но статус плохой — копайте сервер. Предполагаем REST API на Go с Gin или net/http.
-
Логи сервера: Проверьте stdout/stderr или structured logs (Zap/Logrus). Ищите ошибки: "panic: nil pointer" или "database connection failed". В production — ELK stack (Elasticsearch) или CloudWatch.
-
Handler inspection: В коде handler'а для /api/save проверьте:
- Валидация: Используйте binding (Gin: c.ShouldBindJSON) — если fields missing, возвращает 400.
- Auth/Middleware: JWT expired? Rate limiting?
- Business logic: Успешный save, но no response (e.g., defer close без write).
Пример Go-handler'а для формы (POST /api/save с JSON body):
package main
import (
"database/sql"
"encoding/json"
"log"
"net/http"
"github.com/gin-gonic/gin"
_ "github.com/lib/pq" // Для PostgreSQL
)
type FormData struct {
Name string `json:"name" binding:"required,min=2"`
Email string `json:"email" binding:"required,email"`
}
func saveFormHandler(c *gin.Context) {
var data FormData
if err := c.ShouldBindJSON(&data); err != nil {
log.Printf("Validation error: %v", err) // Лог для дебага
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Подключение к БД (в реальности — pool)
db, err := sql.Open("postgres", "user=postgres dbname=forms sslmode=disable")
if err != nil {
log.Printf("DB connection error: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Database unavailable"})
return
}
defer db.Close()
// SQL insert с prepared statement для безопасности
stmt, err := db.Prepare("INSERT INTO forms (name, email) VALUES ($1, $2) RETURNING id")
if err != nil {
log.Printf("Prepare error: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "SQL prepare failed"})
return
}
defer stmt.Close()
var id int
err = db.QueryRow(stmt, data.Name, data.Email).Scan(&id)
if err != nil {
log.Printf("Insert error: %v", err) // e.g., unique constraint violation
if err.Error() == "duplicate key" { // Простая обработка
c.JSON(http.StatusConflict, gin.H{"error": "Email already exists"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "Save failed"})
return
}
// Успех: Вернуть ID или redirect
c.JSON(http.StatusCreated, gin.H{"id": id, "message": "Saved successfully"})
}
func main() {
r := gin.Default()
r.POST("/api/save", saveFormHandler)
log.Fatal(r.Run(":8080"))
}
Здесь silent fail возможен, если log не настроен или err не возвращается (e.g., forget c.JSON в catch). Тестируйте с curl: curl -X POST -H "Content-Type: application/json" -d '{"name":"test","email":"invalid"}' http://localhost:8080/api/save — увидите 400.
- База данных: Если handler падает на SQL:
- Проверьте connection string, pool size (max open connections exceeded?).
- Query:
SELECT * FROM forms WHERE email = 'test@example.com';— дубликат? Транзакция не committed? - Пример SQL для дебага: Добавьте logging query в handler (но не в prod: используйте pprof или EXPLAIN ANALYZE).
4. Инфраструктура и end-to-end проверки
- Сервер логи/метрики: Проверьте nginx/Apache logs (access/error.log) — запрос дошел? 502 если upstream down.
- Воспроизведение: Создайте минимальный repro (e.g., Postman collection). Тестируйте locally vs staging.
- Мониторинг: Если в кластере (Kubernetes), проверьте pods:
kubectl logs pod-name. Metrics: CPU/メモри spike? - Безопасность: CSRF token missing? (Форма без <input type="hidden" name="_csrf">).
5. Завершение диагностики и prevention
Документируйте: "Issue: No request sent due to JS error in event listener. Fix: Added null-check." Для prevention: Добавьте error boundaries в JS (React ErrorBoundary), centralized logging (Sentry), unit-тесты handler'а (Go: httptest).
Этот workflow позволяет диагностировать 90% issues за 5–10 мин, фокусируясь на layers. В команде эскалируйте с скриншотами DevTools/Network для коллаборации.
Вопрос 4. В чем разница между SoapUI и Postman для тестирования API?
Таймкод: 00:14:31
Ответ собеседника: неполный. Postman предназначен для HTTP-запросов с JSON, SoapUI - для SOAP-протокола на базе XML. Оба инструмента позволяют работать с различными форматами, но Postman чаще используется для REST, а SoapUI для SOAP.
Правильный ответ:
SoapUI и Postman — два популярных инструмента для тестирования API, но они различаются по фокусу, возможностям и сценариям применения, что делает выбор зависящим от типа API (REST vs SOAP), сложности тестов и интеграции в workflow разработки. Postman ориентирован на современные, легковесные HTTP-based API (в основном RESTful с JSON), предлагая интуитивный интерфейс для быстрого прототипирования и коллаборации. SoapUI, напротив, изначально создан для SOAP-протоколов (XML-based web services с WSDL), но эволюционировал в универсальный инструмент для functional, load и security testing, особенно в enterprise-окружениях. В контексте Golang-разработки (где API часто строятся на net/http или Gin для REST), Postman чаще используется для daily testing endpoints, в то время как SoapUI подходит для legacy SOAP-интеграций или комплексных тестовых suites. Я разберу различия по ключевым аспектам: протоколы, UI/функциональность, автоматизация, интеграция и pros/cons, с примерами, чтобы показать, как применять их на практике. Это поможет понять, когда выбрать один над другим, и как они дополняют unit-тесты в Go (e.g., httptest) или интеграционные с SQL.
1. Поддержка протоколов и форматов
-
Postman: Фокус на HTTP/HTTPS (GET, POST, PUT, DELETE и т.д.), идеален для REST API, GraphQL, gRPC (через plugins) и WebSockets. Поддерживает JSON, XML, form-data, binary (файлы), но XML не так удобен, как в SoapUI. Нет нативной поддержки WSDL/SOAP — для SOAP приходится вручную строить XML-запросы, что неэффективно. Пример: Тестирование Go REST API для создания пользователя — отправка JSON payload.
В Postman: Создайте request POST /users, Body > raw > JSON:
{
"name": "John Doe",
"email": "john@example.com"
}Headers: Content-Type: application/json. Это быстро проверяет endpoint, возвращающий 201 Created.
-
SoapUI: Специализирован на SOAP 1.1/1.2 (XML envelopes с namespaces), автоматически импортирует WSDL для генерации тестов (operations, ports). Также поддерживает REST (с JSON/XML), но его сила в SOAP-тестировании: assertions для XML schemas, attachments (MTOM). Для REST проще, чем в старых версиях, но UI менее polished. Пример: Для SOAP-сервиса (e.g., legacy банковский API) импортируйте WSDL — SoapUI сгенерирует request template:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<createUser xmlns="http://example.com/services">
<name>John Doe</name>
<email>john@example.com</email>
</createUser>
</soap:Body>
</soap:Envelope>Это автоматически валидирует XML против XSD, чего Postman не делает нативно.
Важный момент: Если ваш API — чистый REST на Go, Postman сэкономит время; для hybrid (SOAP+REST) или compliance с SOAP standards (e.g., WS-Security) — SoapUI.
2. Интерфейс и функциональность тестирования
-
Postman: User-friendly, визуальный: Collections для организации requests (e.g., suite для auth/login/create/delete), Environments/Variables (dev/prod URLs, tokens), Pre-request/Tests scripts на JS для chaining (e.g., получить token из login, использовать в next request). Mock servers для симуляции API без бэкенда. Monitors для scheduled runs. Assertions простые: pm.test("Status is 200", () => pm.response.to.have.status(200)); или JSON parsing: pm.expect(jsonData.id).to.be.a('number').
Pros: Быстрое onboarding, визуальные flows (Postman Flows для no-code testing). Cons: Меньше depth для complex assertions (e.g., XML validation требует custom JS).
-
SoapUI: Более техничный UI с tree-view для projects/testsuites/testcases/steps. Поддерживает data-driven testing (Excel/CSV для inputs), conditional steps (Groovy if-else), load testing (multi-threading до 1000 users), security scans (SQL injection, fuzzing). Assertions мощные: XPath для XML, JSONPath для JSON, compliance checks (SOAP faults). Groovy scripting для custom logic (e.g., парсинг response, DB queries).
Pros: Глубокий functional testing из коробки, visual test editor. Cons: Крутая кривая обучения, UI устаревший по сравнению с Postman.
В Go-проекте: Postman удобен для exploratory testing вашего /api/v1/users endpoint — запустите collection, проверьте response time <200ms. SoapUI подойдет, если API интегрируется с SOAP-legacy, для load-теста (e.g., 500 concurrent POSTs).
3. Автоматизация и scripting
-
Postman: JS-based scripts (pre-request для setup, tests для validation). Автоматизация через Newman (CLI):
newman run collection.json -e env.json --reporters cli,html. Интеграция с CI/CD (Jenkins, GitHub Actions) — генерирует reports (JUnit XML). Для programmatic: Postman API для создания collections.Пример Newman для Go API теста:
newman run postman_collection.json -e production.env --env-var "baseUrl=http://localhost:8080" --reporters htmlВ CI: Это запустит тесты на каждый build, проверяя, что Go handler возвращает правильный JSON.
-
SoapUI: Groovy (Java-like) для scripting — мощнее JS для enterprise (e.g., integrate с JDBC для DB checks). Автоматизация: Command-line runner (testrunner.bat), Maven plugin для CI. ReadyAPI (pro) добавляет advanced reporting, data gen. Для load: Built-in VU (virtual users) с ramp-up.
Пример Groovy в SoapUI для assertion (проверить SQL после POST):
// В TestStep: Groovy Script
import groovy.sql.Sql
sql = Sql.newInstance('jdbc:postgresql://localhost:5432/mydb', 'user', 'pass', 'org.postgresql.Driver')
def userId = context.expand('${createUser#Response#//id}') // XPath из response
def row = sql.firstRow("SELECT * FROM users WHERE id = ${userId}")
assert row.email == 'john@example.com' : 'Email mismatch in DB'
sql.close()Это напрямую проверяет, что Go handler (с SQL insert) сохранил данные correctly — идеально для integration tests.
Оба инструмента покрывают API testing без кода, но SoapUI глубже для scripted validations, Postman — для agile teams.
4. Интеграция, коллаборация и экосистема
-
Postman: Cloud-first: Team workspaces, version control (forks), public collections. Интеграция с Git, Slack, Jira. API для automation (e.g., generate tests from OpenAPI spec). Бесплатный для basics, paid для teams ($12/user/mo).
-
SoapUI: Open-source (SoapUI OS) для solo, ReadyAPI (paid, ~$700/user/yr) для enterprise с support. Интеграция с Jenkins/Maven, но меньше cloud-features. Project files (.xml) для sharing, но не так seamless, как Postman.
В Go CI/CD: Postman + Newman в pipeline для smoke tests; SoapUI для compliance в regulated industries (finance, healthcare).
5. Pros, Cons и when to choose
- Postman Pros: Легкий, быстрый, modern UX, отличен для REST/JSON, коллаборация. Cons: Слаб в SOAP/XML, load testing basic (нужен k6 или JMeter).
- SoapUI Pros: Мощный для SOAP, comprehensive testing (functional/load/security), data-driven. Cons: Сложный, heavier footprint, dated UI.
- Выбор: Postman для 90% modern API (Go REST services) — exploratory + automation. SoapUI для SOAP-heavy или когда нужны advanced assertions/load (e.g., тестирование Go proxy к SOAP backend). В mixed: Используйте оба или мигрируйте к Postman с plugins.
В итоге, Postman упрощает daily dev testing, делая API robust (e.g., validate Go responses на schema), а SoapUI — для thorough validation в complex scenarios. Для подготовки к интервью: Практикуйте создание collections в Postman для вашего Go проекта и импортируйте WSDL в SoapUI, чтобы увидеть workflow differences. Это повысит efficiency в team settings.
Вопрос 5. Что использовали в Charles Proxy для тестирования и как применяли на практике?
Таймкод: 00:16:38
Ответ собеседника: правильный. Отслеживали и перехватывали запросы, изменяли статус-коды и параметры для проверки реакции системы. Пример: изменили код на 503, чтобы убедиться, что фронтенд правильно отображает ошибку, хотя изначально не показывал.
Правильный ответ:
Charles Proxy (часто называемый просто Charles) — это мощный HTTP/HTTPS proxy и debugging tool, который позволяет перехватывать, инспектировать и модифицировать сетевой трафик между клиентом (браузер, мобильное app, фронтенд) и сервером. В контексте тестирования API, особенно для веб- и мобильных приложений на Golang-бэкенде, он незаменим для симуляции реальных сценариев: от анализа запросов до создания edge-кейсов вроде сетевых сбоев или кастомных responses. На практике его применяют в QA и dev для exploratory testing, без необходимости менять код сервера — это ускоряет выявление багов в error handling, caching или UI-resilience. В enterprise-проектах интегрируют с CI/CD для automated mocking, но чаще используют manually для debugging. Я опишу ключевые функции, которые применяются в тестировании, с практическими примерами из реальных сценариев (включая Go API и SQL-интеграции), фокусируясь на том, как это помогает локализовать silent failures или слабые места в системе. Это особенно полезно для senior-разработки, где нужно обеспечивать robustness под нагрузкой или в offline-режимах.
Основные функции Charles Proxy в тестировании
Charles работает как man-in-the-middle proxy: устанавливаете его на Mac (native) или Windows/Linux (via Java), настраиваете SSL certificate для HTTPS (чтобы расшифровывать трафик), и направляете трафик через localhost:8888. Ключевые инструменты для тестирования:
-
Overview и Structure tabs: Визуализация всего трафика — запросы/ответы по host/path, с фильтрами (e.g., только /api/v1/users). Здесь отслеживаете latency, size, methods (GET/POST). Практика: В проекте с Go REST API (Gin framework) анализировали, почему POST /users занимает >2s — увидели, что SQL query в handler блокируется (long-running SELECT без index).
-
Request/Response inspectors: Детальный разбор headers, body (JSON/XML), query params. Поддержка pretty-print, search (e.g., найти "Authorization: Bearer invalid-token"). Практика: Для auth-testing перехватывали JWT в header, копировали и тестировали expiration — если token expired, сервер возвращает 401, но фронтенд (React) не показывал login prompt; фикс — добавить в JS fetch .catch для 401 redirect.
-
Breakpoints и Rewrite tool: Остановка запроса для manual edit (params, headers, body) или auto-модификация (rules-based). Это core для симуляции ошибок. Практика: Как в вашем примере, меняли status code на 503 (Service Unavailable) для /api/save endpoint — если фронтенд не отображает "Сервер недоступен", добавляли в Go middleware retry logic с exponential backoff.
-
Throttling и Bandwidth simulation: Замедление трафика (e.g., 3G speed, 100ms latency) или лимит bandwidth. Практика: Тестировали мобильное app (iOS с Alamofire calls к Go API) — при slow network request timeout'ился, но app crash'ился без offline-cache; реализовали в Go response с Cache-Control: max-age=300 для stale-while-revalidate.
-
Map Local/Remote и Mocking: Перенаправление запросов на local files или другой server. Практика: Для load-testing маппили /api/heavy-query на mock JSON (сгенерированный из SQL dump), чтобы изолировать БД от фронта — сэкономили время на setup staging DB.
-
Focus и Sequence: Фильтр на конкретный request, replay sequence. Практика: Replay 100x POST /transactions для stress-test — выявили race condition в Go (concurrent SQL updates без mutex), где balance дублировался.
Для HTTPS: Устанавливаете Charles root cert в trust store (Keychain на Mac, или adb install для Android), иначе трафик не расшифруется. В production-тестах осторожно: не на live-трафике, только staging.
Практические применения на примерах
В реальном проекте (e.g., e-commerce app с Go backend на PostgreSQL) Charles использовался на всех этапах: от dev debugging до pre-release QA. Вот breakdown:
-
Отслеживание и диагностика трафика:
- Сценарий: Фронтенд (Vue.js) отправляет form-data на /api/upload, но файл не сохраняется. В Charles: Просмотрели request — Content-Type multipart/form-data, но boundary missing; body обрезан (size limit в nginx?). Фикс: В Go handler добавили c.Request.ParseMultipartForm(10 << 20) для 10MB limit.
- SQL-интеграция: Перехватили GET /orders?user_id=123 — response с JOIN query results. Увидели N+1 problem (много subqueries); оптимизировали в Go с sql.DB.Prepare и one query:
В Charles replay с большим user_id выявил pagination need (limit/offset).
// В Go handler
rows, err := db.Query(`
SELECT o.id, o.amount, p.name
FROM orders o
JOIN products p ON o.product_id = p.id
WHERE o.user_id = $1`, userID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Query failed"})
return
}
defer rows.Close()
// Parse to JSON slice
var orders []Order
for rows.Next() {
var o Order
rows.Scan(&o.ID, &o.Amount, &o.ProductName)
orders = append(orders, o)
}
c.JSON(http.StatusOK, orders)
-
Перехват и модификация для error simulation:
- Ваш пример с 503: В Breakpoints > Edit Response > Status: 503, Body: {"error": "Service temporarily unavailable"}. Если фронтенд (fetch) не handles 5xx — добавляем в JS:
Практика: Аналогично изменили params в GET /balance?account=123 на invalid (e.g., negative amount) — сервер вернул 400, но app не validated client-side; добавили Yup schema в form.
fetch('/api/save', { method: 'POST', body: formData })
.then(response => {
if (!response.ok) {
if (response.status >= 500) {
throw new Error('Server error: ' + response.status); // Показать toast
}
return response.json().then(err => Promise.reject(err));
}
return response.json();
})
.catch(error => {
console.error('API Error:', error);
// UI: Show modal with retry button
showErrorModal(error.message);
});
- Ваш пример с 503: В Breakpoints > Edit Response > Status: 503, Body: {"error": "Service temporarily unavailable"}. Если фронтенд (fetch) не handles 5xx — добавляем в JS:
-
Тестирование security и performance:
- Перехват headers: Убрали X-CSRF-Token — проверили, blocks ли Go middleware (gin-contrib/csrf). Если нет, 403 response с логом.
- Throttling: Симулировали 2G для /api/search — latency >5s, app hung; в Go добавили timeout context:
Для SQL: В Charles mock response с empty array — убедились, что UI shows "No results" вместо crash.
ctx, cancel := context.WithTimeout(c.Request.Context(), 3*time.Second)
defer cancel()
// SQL с context
row := db.QueryRowContext(ctx, "SELECT * FROM products WHERE name ILIKE $1", query)
-
Мобильное и cross-device testing:
- На Android/iOS: Настройте proxy в WiFi settings (host: machine IP, port: 8888), install cert. Практика: Тестировали app calls к Go API — перехватили POST /login, changed password to wrong — app showed generic error; улучшили с specific messages from server JSON.
Интеграция и best practices
- С другими tools: Charles + Postman (export requests), или Selenium/Appium для automated flows (script breakpoints via API). В CI: Используйте mitmproxy (open-source alternative) для scripted tests.
- Tips: Enable "Focus" для noisy traffic; save sessions (.chls) для repro; avoid prod (compliance issues). Performance: Charles heavy on RAM — используйте filters.
- В Go-проекте: После Charles-debug добавьте unit-тесты с httptest для mocked responses:
func TestSaveHandler_With503Simulation(t *testing.T) {
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/save", strings.NewReader(`{"data":"test"}`))
// Simulate 503 via middleware mock
c, _ := gin.CreateTestContext(w)
c.Request = req
// Call handler, assert w.Code == 200 (or handle error)
}
В итоге, Charles ускоряет диагностику на 50–70%, позволяя тестировать resilience без infra changes. На практике он выявил множество subtle bugs, как unhandled 5xx в UI или inefficient SQL, делая систему более fault-tolerant. Для подготовки: Установите Charles trial и протестируйте свой Go API — начните с breakpoint на любимом endpoint, чтобы освоить workflow.
Вопрос 6. На каком уровне знания HTML, CSS и Figma для фронтенд-тестирования?
Таймкод: 00:18:00
Ответ собеседника: правильный. Хорошо знаю HTML и CSS, использовал DevTools для инспекции элементов, проверки отображения кнопок, изображений и цветов по требованиям из Figma, фиксировал несоответствия в баг-репортах.
Правильный ответ:
Для фронтенд-тестирования в роли QA или full-stack developer (с фокусом на Golang-бэкенде) уровень знаний HTML, CSS и Figma должен быть практическим и достаточным для инспекции, верификации и автоматизации UI/UX, без глубокого погружения в разработку (в отличие от фронтенд-специалиста). Это intermediate уровень: понимать структуру и стили для выявления багов в rendering, responsiveness и consistency с дизайном, интегрируя с API-тестированием (e.g., как Go endpoint рендерит data в UI). В production-проектах это позволяет быстро локализовать issues вроде mismatched layouts или broken interactions, минимизируя циклы feedback с дизайнерами/devs. Я опишу рекомендуемый уровень по каждому инструменту, с практическими примерами из тестирования (включая DevTools, visual diffs и интеграцию с бэкендом), чтобы показать, как применять это для end-to-end validation. Это особенно актуально для API-driven apps, где фронт (React/Vue) потребляет JSON от Go, и баги могут быть в data-binding или CSS overrides.
1. HTML: Уровень — уверенное понимание семантики и DOM
Нужен solid grasp базовых тегов, атрибутов и структуры для инспекции и манипуляции элементами, плюс awareness accessibility (a11y) и SEO. Не обязательно писать сложный markup, но уметь читать/дебагить его.
-
Ключевые знания:
- Семантика:
<header>,<nav>,<main>,<section>,<button>,<img alt="">— проверять на compliance с ARIA (e.g., role="button" для custom элементов). - Формы:
<form>,<input type="email">, validation attributes (required, pattern) — тестировать client-side validation перед API call. - DOM: Понимание tree (parent/child), IDs/classes для селекторов. Edge-кейсы: Nested elements, shadow DOM в web components.
- Семантика:
-
Практика в тестировании:
- В DevTools (Elements tab): Инспектировать кнопку "Submit" — убедиться, что
<button type="submit">внутри<form>, и onclick не переопределяет default behavior. Если форма не отправляет data на Go /api/submit, проверить, нет лиevent.preventDefault()без AJAX fallback. - Пример бага: В e-commerce app (Go backend рендерит product list via templates или JSON для SPA) —
<img src="/api/image?id=123">не загружается (404). Тест: Right-click > Inspect, скопировать src, curl в terminal — если Go handler missing, report как "Broken image endpoint". - Автоматизация: Используйте Playwright/Puppeteer для assertion:
expect(page.locator('button[type="submit"]')).toBeVisible();— проверяет presence в DOM после API response. - Связь с бэкендом: В Go с html/template, тестируйте SSR: Endpoint /profile возвращает HTML с {{.User.Name}}, inspect — name escaped correctly (XSS prevention via html.EscapeString).
- В DevTools (Elements tab): Инспектировать кнопку "Submit" — убедиться, что
Уровень: Достаточно, чтобы фиксировать 80% UI bugs (e.g., missing alt на images для a11y compliance), без написания custom components.
2. CSS: Уровень — рабочие знания селекторов, layouts и responsive
Фокус на инспекции стилей, overrides и media queries, чтобы верифицировать visual fidelity. Не deep Flexbox/Grid dev, но умение debug стили для cross-browser consistency.
-
Ключевые знания:
- Селекторы: Class (#id, .class), pseudo (:hover, :focus), combinators (> child). Specificity (inline > id > class).
- Layouts: Box model (margin/padding/border), Flexbox (justify-content: center), Grid basics. Transitions/animations (@keyframes).
- Responsive: Media queries (@media (max-width: 768px)), viewport meta. Units (rem/em vs px).
- Preprocessors: Awareness SASS/LESS (variables, nesting) — для чтения compiled CSS.
-
Практика в тестировании:
- DevTools (Styles/Computed tabs): Выбрать элемент (e.g., button) — проверить color: #007BFF из Figma, нет ли override от user-agent stylesheet. Toggle :hover — animation smooth? Если нет, bug в CSS transition.
- Пример: Тестирование dashboard (data от Go /api/metrics, rendered в CSS Grid). Resize window — grid collapses на mobile? Inspect media query, assert columns: 1 на <480px. Если button color wrong (e.g., #FF0000 вместо design #4CAF50), screenshot + hex comparison в report.
- Cross-browser: Тестируйте в Chrome/Firefox/Safari — CSS3 properties (e.g., backdrop-filter) polyfill'ятся? Используйте BrowserStack для remote devices.
- Visual regression: Tools вроде Percy или BackstopJS — baseline screenshot из Figma, compare after build. Пример CSS issue: Button padding 10px в desktop, но 5px в mobile из-за !important override — fix в dev CSS.
- Интеграция с Go: Если API возвращает dynamic classes (e.g., JSON {"status": "error", "class": "alert-danger"}), inspect — applied ли CSS .alert-danger { background: red; }? Тест: Mock response в Charles Proxy (как ранее), verify UI reaction.
Уровень: Уметь debug 90% styling bugs (e.g., z-index overlaps), используя DevTools forces (override styles temporarily) для quick fixes в repro.
3. Figma: Уровень — proficient чтение и экспорт дизайна
Не design skills, а ability использовать Figma как spec tool: Измерять, экспортировать assets, проверять states/prototypes. Это bridge между design и testing.
-
Ключевые знания:
- Interface: Frames/artboards, layers, components (variants для states: default/hover/disabled). Auto-layout для responsive.
- Measurements: Ruler tool для spacing (e.g., 16px margin), color picker (hex/RGB), typography (font-size, line-height).
- Interactions: Prototype mode — кликать flows, проверить transitions (e.g., form submit → success modal).
- Plugins: Inspect для CSS/JSON export, Zeplin/Avocode для sharing specs.
-
Практика в тестировании:
- Сравнение: Откройте Figma file (shared link), inspect button: Width 200px, height 48px, border-radius 8px, shadow 0 2px 4px rgba(0,0,0,0.1). В app — DevTools measure, diff: Если radius 4px, bug report с annotations (e.g., "Mismatch: Expected 8px, got 4px").
- States: Figma variant hover — color changes to #0056b3. Тест: Hover в browser, verify. Для forms: Prototype submit — modal appears; в app, после POST /api/form (Go handler), check modal trigger.
- Assets: Export SVG/PNG из Figma, compare с rendered <img> — resolution match? Для icons: Vector vs raster scaling.
- Пример workflow: В project с Go API для user dashboard — Figma mockup с chart (data от /api/stats). Экспорт CSS snippet из Figma Inspect:
В DevTools: Paste в console, apply — matches ли original? Если chart data empty (API bug), UI shows placeholder correctly?
.chart-container {
width: 100%;
height: 300px;
background: #f5f5f5;
} - Tools integration: Figma to Storybook для component testing, или Adobe XD alternatives, но Figma dominant для collab.
Уровень: Уметь verify 100% visual specs без design tools mastery, фокусируясь на pixel-perfect (или 95% tolerance для responsive).
Общий подход к фронтенд-тестированию с этими знаниями
- Workflow: 1) Review Figma spec. 2) Manual test в browser/DevTools. 3) Automate с Cypress:
cy.get('button').should('have.css', 'background-color', 'rgb(76, 175, 80)');. 4) Report в Jira: Screenshots (Figma vs actual), steps to repro, severity (e.g., high для broken submit). - Интеграция с бэкендом: Тестируйте data-driven UI — Go /api/user возвращает {"avatar": "url"}, inspect <img src=...> loads, CSS .avatar { border-radius: 50%; }. Если SQL query в Go fails (e.g., no rows), UI fallback (empty state) matches Figma?
- Best practices: Cross-device (mobile/desktop), dark mode (CSS prefers-color-scheme), performance (Lighthouse audit для CSS bloat). Уровень растет с практикой: Начните с simple forms, перейдите к complex layouts.
- Для Golang dev: Это усиливает full-stack testing — debug, как API response влияет на CSS classes (e.g., conditional rendering), используя Go's html/template или JSON для SPA.
Такой уровень позволяет independently handle UI bugs, ускоряя delivery, и интегрировать с backend validation (e.g., schema для JSON shapes). В интервью демонстрируйте примерами из DevTools screenshots — это покажет hands-on experience.
Вопрос 7. Как работали с логами в Kibana?
Таймкод: 00:19:08
Ответ собеседника: неполный. Применял фильтры для поиска нужных записей, использовал Google для понимания и анализа логов.
Правильный ответ:
Kibana — это фронтенд-интерфейс для Elasticsearch в ELK Stack (Elasticsearch, Logstash, Kibana), который позволяет в реальном времени искать, анализировать и визуализировать логи из распределенных систем, включая Golang-приложения с API, микросервисами и базами данных. В практике тестирования и debugging это инструмент для root cause analysis: от выявления ошибок в HTTP-handlers до мониторинга производительности SQL-запросов, без необходимости grep'а по файлам. На senior-уровне вы интегрируете его в CI/CD для proactive alerting, создавая dashboards для ключевых метрик (e.g., error rate >5%), и используете для correlation событий (trace ID для distributed tracing). В проектах с Go (e.g., Gin/Echo servers) логи структурированы (JSON с fields: level, timestamp, service, request_id), чтобы Kibana мог их парсить эффективно. Я разберу workflow от setup до advanced usage, с примерами из реальных сценариев (debugging API failures и SQL bottlenecks), фокусируясь на том, как это ускоряет incident response и улучшает observability. Это особенно полезно для high-traffic apps, где логи — единственный источник правды после deploys.
1. Подготовка и интеграция логов
Перед работой с Kibana настройте pipeline: Go app генерирует логи → shipper (Filebeat/Fluentd) собирает → Logstash парсит/фильтрует → Elasticsearch индексирует → Kibana queries. Для Go используйте structured logging libraries вроде Uber Zap или Logrus, чтобы выводить JSON, совместимый с ELK.
-
Go-пример: Structured logging в API handler В production Go-сервисе (e.g., REST API с PostgreSQL) добавьте logging middleware:
package main
import (
"context"
"net/http"
"time"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
var logger *zap.Logger // Инициализируйте в main: logger, _ = zap.NewProduction()
func loggingMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
reqID := c.GetHeader("X-Request-ID") // Или генерируйте UUID
if reqID == "" {
reqID = generateUUID() // Custom func
c.Header("X-Request-ID", reqID)
}
// Log request
logger.Info("Incoming request",
zap.String("method", c.Request.Method),
zap.String("path", c.Request.URL.Path),
zap.String("request_id", reqID),
zap.String("client_ip", c.ClientIP()),
zap.String("user_agent", c.Request.UserAgent()))
c.Next()
// Log response
latency := time.Since(start)
logger.Info("Request completed",
zap.String("request_id", reqID),
zap.Int("status", c.Writer.Status()),
zap.Duration("latency", latency),
zap.Error(c.Errors.LastErr())) // Если error
if c.Writer.Status() >= 500 {
logger.Error("Server error occurred",
zap.String("request_id", reqID),
zap.Int("status", c.Writer.Status()),
zap.Stack("stacktrace")) // Для panic recovery
}
}
}
func main() {
r := gin.Default()
r.Use(loggingMiddleware())
r.GET("/api/users/:id", getUserHandler) // Handler с SQL query
r.Run(":8080")
}
func getUserHandler(c *gin.Context) {
id := c.Param("id")
var user User
err := db.QueryRowContext(c.Request.Context(), "SELECT id, name, email FROM users WHERE id = $1", id).Scan(&user.ID, &user.Name, &user.Email)
if err != nil {
logger.Error("SQL query failed",
zap.String("request_id", c.GetHeader("X-Request-ID")),
zap.Error(err),
zap.String("query", "SELECT ... WHERE id = $1")) // Log query для analysis
c.JSON(http.StatusInternalServerError, gin.H{"error": "User not found"})
return
}
c.JSON(http.StatusOK, user)
}Логи выводятся в stdout или file (e.g., /var/log/app.jsonl), Filebeat читает и ships в Elasticsearch с fields: @timestamp, log.level, service.name="go-api".
-
Setup в Kibana: После индексации (Index Patterns: logstash-* или custom go-app-*) создайте index pattern в Management > Stack Management > Index Patterns. Укажите timestamp field (@timestamp) для time-based queries. Для SQL-логов: Парсите query field в Logstash с grok filter (e.g., match query "SELECT ...").
2. Базовый поиск и фильтрация логов
Kibana's Discover — основной экран: Time picker (last 15min/24h), search bar с Lucene/KQL queries.
- Поиск: Используйте KQL (Kibana Query Language):
service.name: go-api AND status: 500— найдет ошибки в Go API. Для free-text:error AND "user not found". - Фильтры: Add filter (e.g., request_id = "abc123") для drill-down. Pin filters для reuse. В вашем случае, помимо базовых, применяйте aggregations: Histogram по latency (buckets: 0-100ms, 100-500ms) — выявить slow queries.
- Практика: Debugging 500 error в /api/users. Query:
method: GET AND path: /api/users/* AND status: 500. Results: Видите log с SQL err "pq: syntax error" — корень в parametrized query (e.g., id с injection, но prepared statement спас). Фильтр по timestamp: Сравните до/после deploy — spike ошибок после change в handler.
Для SQL: Query: query: *SELECT* AND latency > 1000ms — аггрегируйте avg duration, выявите N+1 (много identical queries по user_id). Export results в CSV для deep analysis.
3. Визуализация и dashboards
Создайте visualizations (Lens или TSVB) для monitoring.
-
Примеры:
- Line chart: Error rate over time (
status >= 400 / total requests) — alert если >10%. - Pie chart: Top errors by message (e.g., "SQL timeout" 40%, "Auth failed" 30%).
- Heatmap: Latency по endpoints (x: time, y: path) — hot spots в /api/transactions с JOINs.
- Line chart: Error rate over time (
-
Dashboards: Combine visualizations в custom board (e.g., "Go API Health"). Add controls (dropdown для env: dev/prod). Практика: В e-commerce проекте dashboard показал correlation: Spike в SQL logs ("deadlock detected") во время peak traffic — fix via Go's context timeout и DB index on transaction_id.
- SQL-specific: Visualize query counts: Bar chart по query patterns (grok parse: SELECT users → 2000 calls) — optimize с EXPLAIN в logs.
Save/share dashboards для team; embed в Grafana если hybrid stack.
4. Advanced features: Alerts, ML и correlation
- Alerts: В Stack Management > Alerts — rule на query (e.g.,
status: 500 > 5 in 5min). Action: Slack notification или PagerDuty. Практика: Alert на "Go panic" (log.level: ERROR AND stacktrace present) — auto-scale pods в K8s. - Machine Learning jobs: Anomaly detection (e.g., unusual latency spike) — job на timeseries data. Практика: ML выявил anomaly в SQL logs (sudden increase в "connection pool exhausted") — root cause: Go DB pool size=10, traffic x10; increased to 50.
- Correlation: Используйте trace_id (от OpenTelemetry или custom) для linking logs: Filter по request_id — see sequence: Frontend call → Go handler log → SQL log → Response. В distributed systems (Go microservices) это traces full flow.
Для SQL: Интегрируйте pganalyze или log slow queries (>500ms) в Go с db.Stats(), log в ELK — Kibana query: db.query_duration > 500 AND db.error: true.
5. Best practices и troubleshooting
- Performance: Limit indices (ILM policy: rollover daily), use sampling для large datasets. Avoid * in queries — specify fields (e.g., message: "error*").
- Security: Role-based access (Kibana spaces для teams), anonymize PII в logs (Go: zap.Redact).
- Troubleshooting: Нет логов? Check Filebeat status (logs/errors), Elasticsearch health (_cat/health). Slow Kibana? Increase heap (ES_JAVA_OPTS). Практика: В incident'e (API down) — timeline query по @timestamp, filter errors — found "Elasticsearch cluster red" из-за disk full; rotated indices.
- Интеграция с Go testing: В CI (GitHub Actions) ship test logs to ELK, query в post-test для coverage (e.g., assert no ERROR logs).
В итоге, Kibana превращает raw logs в actionable insights: От simple filter для quick debug до ML для predictive maintenance. В Go-проектах это снижает MTTR (mean time to resolution) на 60%, позволяя proactive fixes (e.g., SQL tuning по log patterns). Для подготовки: Установите local ELK (Docker Compose), генерируйте Go logs, practice queries — начните с Discover, перейдите к alerts, чтобы освоить full cycle.
Вопрос 8. Написать SQL-запрос для таблицы 'clients' с колонками фамилия, имя, отчество, дата_рождения: вывести фамилии и даты рождения клиентов старше 30 лет.
Таймкод: 00:19:41
Ответ собеседника: неполный. SELECT фамилия, дата_рождения FROM clients WHERE дата_рождения <= '1994-05-15'; Рассчитал дату как текущая минус 30 лет, учитывая формат даты.
Правильный ответ:
SQL-запрос для фильтрации клиентов старше 30 лет требует аккуратной работы с датами, чтобы избежать ошибок в расчете возраста, особенно с учетом текущей даты, високосных годов и timezone. В таблице 'clients' предполагаем стандартную структуру: id (SERIAL или INT), фамилия (VARCHAR), имя (VARCHAR), отчество (VARCHAR), дата_рождения (DATE). Для "старше 30 лет" вычисляем границу как CURRENT_DATE минус 30 лет, но с учетом дня рождения: клиент старше 30, если дата_рождения + 30 лет < CURRENT_DATE. Это точнее, чем простое сравнение с фиксированной датой, так как фиксированная (как в ответе собеседника) устареет и не учитывает динамику. В production (e.g., Golang app с PostgreSQL или MySQL) такой запрос используется в API-handlers для отчетов или дашбордов, с пагинацией и индексами для performance. Я приведу варианты для популярных СУБД (PostgreSQL — предпочтительна для Go из-за сильной поддержки JSON и concurrency; MySQL для legacy), объясню нюансы, edge-кейсы и интеграцию с Go (используя database/sql и pgx для Postgres). Это обеспечит scalability: запрос O(log N) с B-tree index на дата_рождения, минимизируя full scan на миллионах записей.
1. Базовый подход и расчет возраста
- Логика: Возраст = CURRENT_DATE - дата_рождения (в годах). Но для точности: Если день рождения уже прошел в текущем году, возраст полный; иначе — минус 1. Простой фильтр: WHERE дата_рождения <= ADD_MONTHS(CURRENT_DATE, -360) (примерно 30 лет), но лучше DATEADD(YEAR, -30, дата_рождения) < CURRENT_DATE.
- Вывод: Только фамилия и дата_рождения (ORDER BY фамилия для consistency).
- Предположения: Колонки на русском — используем double quotes в SQL (стандарт ANSI). Дата в формате 'YYYY-MM-DD'. Нет NULL в дата_рождения (если есть, добавить WHERE дата_рождения IS NOT NULL).
2. SQL-запрос для PostgreSQL (рекомендуется для Go)
PostgreSQL имеет мощные date functions: AGE() для интервала, или DATE_TRUNC для precision. Запрос возвращает клиентов, чей день рождения был ≥30 лет назад.
-- Базовый запрос: Клиенты старше 30 лет
SELECT
"фамилия" AS surname,
"дата_рождения" AS birth_date
FROM clients
WHERE "дата_рождения" IS NOT NULL
AND AGE(CURRENT_DATE, "дата_рождения") > INTERVAL '30 years'
ORDER BY "фамилия" ASC;
-- Альтернатива с явным расчетом (если AGE не нужен)
SELECT
"фамилия" AS surname,
"дата_рождения" AS birth_date
FROM clients
WHERE "дата_рождения" IS NOT NULL
AND "дата_рождения" <= CURRENT_DATE - INTERVAL '30 years'
ORDER BY "фамилия" ASC;
-- С вычисленным возрастом (для отчета)
SELECT
"фамилия" AS surname,
"дата_рождения" AS birth_date,
EXTRACT(YEAR FROM AGE(CURRENT_DATE, "дата_рождения")) AS age_years
FROM clients
WHERE "дата_рождения" IS NOT NULL
AND EXTRACT(YEAR FROM AGE(CURRENT_DATE, "дата_рождения")) > 30
ORDER BY age_years DESC, "фамилия" ASC;
-
Объяснение:
- AGE(CURRENT_DATE, дата_рождения) возвращает INTERVAL (e.g., '32 years 5 months'). > '30 years' — точный порог.
- EXTRACT(YEAR FROM AGE(...)) — integer возраст, игнорируя месяцы (для "старше 30" подходит, если день рождения прошел).
- ORDER BY: Сортировка по фамилии; в отчете — по возрасту для insights.
- Performance: Добавьте INDEX:
CREATE INDEX idx_clients_birth ON clients ("дата_рождения");— ускорит WHERE до <1ms на 1M rows (EXPLAIN ANALYZE покажет Index Scan).
-
Edge-кейсы:
- Високосный год: 29 февраля 2000 — в 2030 (невисокос) возраст 29 до марта? AGE handles correctly.
- Timezone: Если БД в UTC, а app в MSK, используйте CURRENT_DATE AT TIME ZONE 'Europe/Moscow'.
- NULL/Invalid dates: IS NOT NULL + CHECK constraint на insert (дата_рождения < CURRENT_DATE).
- Пустой результат: Если нет клиентов >30, возвращает 0 rows — в Go handle как [].
3. SQL-запрос для MySQL (альтернатива)
MySQL использует DATE_SUB или TIMESTAMPDIFF. Менее гибкий для интервалов, но работает.
-- Базовый запрос
SELECT
фамилия AS surname,
дата_рождения AS birth_date
FROM clients
WHERE дата_рождения IS NOT NULL
AND TIMESTAMPDIFF(YEAR, дата_рождения, CURDATE()) > 30
ORDER BY фамилия ASC;
-- Альтернатива с DATE_SUB (примерно)
SELECT
фамилия AS surname,
дата_рождения AS birth_date
FROM clients
WHERE дата_рождения IS NOT NULL
AND дата_рождения < DATE_SUB(CURDATE(), INTERVAL 30 YEAR)
ORDER BY фамилия ASC;
-- С возрастом
SELECT
фамилия AS surname,
дата_рождения AS birth_date,
TIMESTAMPDIFF(YEAR, дата_рождения, CURDATE()) AS age_years
FROM clients
WHERE дата_рождения IS NOT NULL
AND TIMESTAMPDIFF(YEAR, дата_рождения, CURDATE()) > 30
ORDER BY age_years DESC, фамилия ASC;
- Объяснение: TIMESTAMPDIFF(YEAR, ..., CURDATE()) — integer >30. DATE_SUB для boundary. В MySQL backticks для колонок:
фамилияесли reserved. - Различия с Postgres: Нет native INTERVAL; TIMESTAMPDIFF округляет вниз, так что для дня рождения 15.05.1994 на 15.05.2024 = ровно 30, но >30 если прошел день. Тестируйте с sample data.
- Performance: INDEX на дата_рождения:
CREATE INDEX idx_birth ON clients (дата_рождения);. Для large tables — PARTITION BY RANGE (YEAR(дата_рождения)).
4. Интеграция с Golang: Выполнение запроса в app
В Go-приложении (e.g., API /clients/older-than-30) используйте database/sql с driver (lib/pq для Postgres, go-sql-driver/mysql). Подготовьте statement для security (parametrized, хотя здесь нет vars). Обработайте errors, rows.
package main
import (
"database/sql"
"encoding/json"
"fmt"
"log"
"net/http"
_ "github.com/lib/pq" // Для PostgreSQL
)
type Client struct {
Surname string `json:"surname"`
BirthDate string `json:"birth_date"` // Или time.Time
}
func olderClientsHandler(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
query := `
SELECT "фамилия", "дата_рождения"
FROM clients
WHERE "дата_рождения" IS NOT NULL
AND AGE(CURRENT_DATE, "дата_рождения") > INTERVAL '30 years'
ORDER BY "фамилия" ASC
LIMIT 100; -- Пагинация для large results
`
rows, err := db.Query(query)
if err != nil {
log.Printf("Query error: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
defer rows.Close()
var clients []Client
for rows.Next() {
var c Client
err := rows.Scan(&c.Surname, &c.BirthDate)
if err != nil {
log.Printf("Scan error: %v", err)
continue
}
clients = append(clients, c)
}
if err = rows.Err(); err != nil {
log.Printf("Rows error: %v", err)
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(clients)
}
}
func main() {
db, err := sql.Open("postgres", "host=localhost port=5432 user=postgres password=pass dbname=app sslmode=disable")
if err != nil {
log.Fatal(err)
}
defer db.Close()
http.HandleFunc("/api/clients/older-30", olderClientsHandler(db))
log.Fatal(http.ListenAndServe(":8080", nil))
}
- Объяснение: Query в handler — динамический возраст. LIMIT для pagination (add ?page=1 в URL). Scan в struct для JSON marshal. Errors: Log, но graceful (не expose details). Для MySQL: Замените driver и query на TIMESTAMPDIFF.
- Testing: Unit с sqlmock: Mock rows с sample data (e.g., {Surname: "Иванов", BirthDate: "1980-01-01"}). Integration: Run local DB, curl /api/clients/older-30 — verify JSON.
- Security/Perf: Prepared: db.Prepare(query) для reuse. Context timeout: db.QueryContext(ctx, query). В production: Connection pool (db.SetMaxOpenConns(25)).
5. Дополнительные соображения
- Точность возраста: Для birthday-aware: WHERE (CURRENT_DATE >= дата_рождения + INTERVAL '30 years'). Это исключает ровно 30-летних до дня рождения.
- Масштабирование: На 10M clients — materialized view или denormalize возраст в column (UPDATE trigger). В Go: Cache results (Redis) для frequent queries.
- Валидация: Перед insert в clients: CHECK (дата_рождения < CURRENT_DATE). Тест data: INSERT VALUES ('Петров', ..., '1990-06-20') — должен пройти.
- Ошибки в подходе собеседника: Фиксированная дата ('1994-05-15') — brittle (нужно manual update yearly); лучше dynamic. Нет ORDER/IS NOT NULL — potential issues.
Этот запрос и интеграция обеспечивают reliable, performant вывод, подходящий для reporting в Go apps. В реальном проекте протестируйте с EXPLAIN (Postgres) или SHOW PROFILE (MySQL) для optimization, и добавьте logging (Zap) для query metrics.
Вопрос 9. Написать SQL-запрос для двух таблиц: первая с номером клиента и фамилией, вторая с ID клиента и номером счёта. Вывести фамилии и номера счетов, используя соединение.
Таймкод: 00:24:00
Ответ собеседника: неполный. Использовать JOIN по номеру клиента и ID: SELECT фамилия, номер_счёта FROM clients JOIN accounts ON номер_клиента = ID_клиента.
Правильный ответ:
SQL-запрос для соединения двух таблиц (clients и accounts) — классический случай relational querying, где мы извлекаем связанные данные: фамилии клиентов и их номера счетов. Первая таблица (clients) содержит client_number (INT, primary key) и фамилия (VARCHAR). Вторая (accounts) — client_id (INT, foreign key to clients.client_number) и account_number (VARCHAR, unique). Это типичная one-to-many связь: один клиент может иметь несколько счетов, так что INNER JOIN вернёт дубликаты фамилий для multi-account клиентов. В production (e.g., банковская Golang API с PostgreSQL) такой запрос используется для отчетов (/api/client-accounts), с пагинацией, фильтрами и indexes для scalability на миллионах записей. Я приведу варианты для PostgreSQL/MySQL, объясню типы JOIN, edge-кейсы (e.g., clients без счетов), и интегрирую с Go (database/sql + pgx для Postgres), фокусируясь на security (prepared statements), performance (EXPLAIN) и handling multiple results. Это обеспечивает atomicity и efficiency, минимизируя Cartesian product risks.
1. Предполагаемая структура таблиц
Для clarity, вот DDL (PostgreSQL-style; MySQL аналогично):
-- Таблица clients
CREATE TABLE clients (
client_number SERIAL PRIMARY KEY,
фамилия VARCHAR(100) NOT NULL
);
-- Таблица accounts
CREATE TABLE accounts (
account_number VARCHAR(20) PRIMARY KEY, -- e.g., 'ACC-123456'
client_id INTEGER NOT NULL REFERENCES clients(client_number) ON DELETE CASCADE
);
-- Sample data
INSERT INTO clients (фамилия) VALUES ('Иванов'), ('Петров'), ('Сидоров');
INSERT INTO accounts (account_number, client_id) VALUES
('ACC-001', 1), ('ACC-002', 1), -- Иванов имеет 2 счета
('ACC-003', 2), -- Петров — 1
('ACC-004', 3); -- Сидоров — 1
- Ключевые моменты: FK constraint обеспечивает referential integrity (no orphan accounts). Index на accounts.client_id для fast JOIN.
2. Базовый SQL-запрос: INNER JOIN
INNER JOIN возвращает только matching rows (клиенты со счетами). Если клиент без счетов — не включен. Сортировка по фамилии и номеру счета для readability.
Для PostgreSQL (рекомендуется для Go: strong ACID, JSON support):
-- INNER JOIN: Фамилии и номера счетов (дубли для multi-accounts)
SELECT
c."фамилия" AS surname,
a."account_number" AS account_number
FROM clients c
INNER JOIN accounts a ON c."client_number" = a."client_id"
ORDER BY c."фамилия" ASC, a."account_number" ASC;
- Результат на sample data:
surname | account_number
Иванов | ACC-001
Иванов | ACC-002
Петров | ACC-003
Сидоров | ACC-004 - Объяснение: Alias (c, a) для brevity. Double quotes для русских колонок (ANSI standard). ORDER BY — stable output. Performance: O(N log N) с indexes; без — full scan (EXPLAIN покажет).
Для MySQL (backticks вместо quotes):
SELECT
c.`фамилия` AS surname,
a.`account_number` AS account_number
FROM clients c
INNER JOIN accounts a ON c.`client_number` = a.`client_id`
ORDER BY c.`фамилия` ASC, a.`account_number` ASC;
3. Расширенные варианты
-
LEFT JOIN: Включает всех клиентов, даже без счетов (NULL для account_number). Полезно для отчета "активные/неактивные".
PostgreSQL:
SELECT
c."фамилия" AS surname,
a."account_number" AS account_number
FROM clients c
LEFT JOIN accounts a ON c."client_number" = a."client_id"
ORDER BY c."фамилия" ASC, a."account_number" ASC NULLS LAST; -- Postgres-specific: NULLs в конце- Результат: Добавит Петрова без счета? Нет, в sample все имеют; если добавить клиента без — surname | NULL.
-
С аггрегацией (если unique по клиенту): Для multi-accounts — ARRAY_AGG номеров (Postgres) или GROUP_CONCAT (MySQL).
PostgreSQL (список счетов в JSON array):
SELECT
c."фамилия" AS surname,
ARRAY_AGG(a."account_number" ORDER BY a."account_number") AS account_numbers -- Или json_agg
FROM clients c
INNER JOIN accounts a ON c."client_number" = a."client_id"
GROUP BY c."client_number", c."фамилия"
ORDER BY c."фамилия" ASC;Результат: Иванов | {ACC-001, ACC-002}
MySQL:
SELECT
c.`фамилия` AS surname,
GROUP_CONCAT(a.`account_number` ORDER BY a.`account_number` SEPARATOR ', ') AS account_numbers
FROM clients c
INNER JOIN accounts a ON c.`client_number` = a.`client_id`
GROUP BY c.`client_number`, c.`фамилия`
ORDER BY c.`фамилия` ASC;Результат: Иванов | ACC-001, ACC-002
-
С фильтрами/пагинацией: Для large datasets (e.g., >100k clients).
-- Postgres: LIMIT/OFFSET
SELECT ... FROM clients c INNER JOIN accounts a ON ...
WHERE c."фамилия" ILIKE '%ов%' -- Фильтр по фамилии
ORDER BY ...
LIMIT 10 OFFSET 0; -- Page 1
4. Edge-кейсы и оптимизация
- Multiple accounts: INNER JOIN дублирует фамилии — нормально для flat results; используйте GROUP BY для aggregated.
- No matches: Если нет счетов — 0 rows (INNER); LEFT вернёт всех с NULL.
- NULL/Invalid FK: Constraint предотвращает; тест: INSERT invalid client_id — error.
- Performance:
- Indexes:
CREATE INDEX idx_accounts_client ON accounts (client_id);— JOIN использует Index Nested Loop (EXPLAIN: Seq Scan → bad; Index Scan → good). - На 1M rows: <50ms с index; без — seconds. В Go: Monitor с pg_stat_statements.
- Anti-patterns: Избегайте SELECT * (только needed cols); no WHERE без index.
- Indexes:
- Тестирование: Sample data выше. Unit в DB fiddle или local Postgres: Run query, assert row count=4.
5. Интеграция с Golang: Выполнение в API
В Go app (e.g., Gin handler /api/client-accounts) используйте sql.DB с prepared query для reuse/security. Handle multiple rows в slice. Для Postgres — pgx driver для efficiency.
package main
import (
"database/sql"
"encoding/json"
"fmt"
"log"
"net/http"
"strconv"
_ "github.com/lib/pq" // Postgres driver
)
type ClientAccount struct {
Surname string `json:"surname"`
AccountNumber string `json:"account_number"`
}
func clientAccountsHandler(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Prepared statement для security (хотя нет params; для future filters)
query := `
SELECT c."фамилия", a."account_number"
FROM clients c
INNER JOIN accounts a ON c."client_number" = a."client_id"
ORDER BY c."фамилия" ASC, a."account_number" ASC
LIMIT 100; -- Prevent overload
`
rows, err := db.Query(query)
if err != nil {
log.Printf("Query error: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
defer rows.Close()
var accounts []ClientAccount
for rows.Next() {
var ca ClientAccount
if err := rows.Scan(&ca.Surname, &ca.AccountNumber); err != nil {
log.Printf("Scan error: %v", err)
continue
}
accounts = append(accounts, ca)
}
if err := rows.Err(); err != nil {
log.Printf("Rows iteration error: %v", err)
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(accounts); err != nil {
log.Printf("JSON encode error: %v", err)
}
}
}
func main() {
db, err := sql.Open("postgres", "host=localhost port=5432 user=postgres password=pass dbname=bank sslmode=disable")
if err != nil {
log.Fatal("DB connection error:", err)
}
defer db.Close()
// Test connection
if err := db.Ping(); err != nil {
log.Fatal("Ping error:", err)
}
http.HandleFunc("/api/client-accounts", clientAccountsHandler(db))
fmt.Println("Server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
- Объяснение: Query в handler — simple, но prepared via db.Query (no $1 here). Slice для results (handles multi-accounts). Errors: Log, 500 response. JSON output: Array of objects. Для pagination: Parse ?limit=10&offset=0 из r.URL.Query(), add to query.
- Testing: Unit с sqlmock: Mock rows (e.g., Ivanov | ACC-001), assert len(accounts)==4. Integration: Run DB container (Docker), curl http://localhost:8080/api/client-accounts — verify JSON.
- Advanced: Для LEFT JOIN — handle NULL AccountNumber (e.g., "No accounts"). Context: db.QueryContext(r.Context(), query) для timeout. Pool: db.SetMaxOpenConns(20) для concurrency.
6. Дополнительные соображения
- Безопасность: FK + prepared — prevents injection. В Go: Validate input если add filters (e.g., surname LIKE ?).
- Масштабирование: На large DB — read replicas для SELECTs. В Go: Cache (Redis) для frequent queries (e.g., TTL 5min).
- Различия DBMS: Postgres лучше для complex JOINs (window functions); MySQL — для simple, но watch charset (UTF-8 для русских фамилий).
- Улучшения от ответа собеседника: Добавлен тип JOIN (INNER), quotes/backticks, ORDER, LIMIT. Полный syntax без abbreviations.
Этот подход делает запрос robust и integrable, подходящим для real-time API в Go. В проекте всегда run EXPLAIN перед deploy, и мониторьте slow queries в logs (pgBadger).
Вопрос 10. Написать SQL-запрос для таблицы clients: посчитать количество клиентов с фамилией Иванов.
Таймкод: 00:25:39
Ответ собеседника: правильный. SELECT COUNT(*) FROM clients WHERE фамилия = 'Иванов'; Упомянул агрегатную функцию COUNT и WHERE, с группировкой если нужно.
Правильный ответ:
SQL-запрос для подсчета количества клиентов с фамилией "Иванов" в таблице clients — это базовый пример агрегации, где COUNT(*) суммирует rows, matching условию WHERE. Таблица clients предполагает структуру: id (SERIAL PRIMARY KEY), фамилия (VARCHAR(100) NOT NULL), имя, отчество, дата_рождения и т.д. (из предыдущих вопросов). Это типичный analytical query для отчетов или дашбордов в банковских/CRM-системах, где нужно быстро получить stats по сегментам пользователей. В production (e.g., Golang API с PostgreSQL) такой запрос выполняется в read-only handlers, с кэшированием для frequent calls и индексами для sub-second response на миллионах записей. Я приведу варианты для PostgreSQL (предпочтительно для Go: robust concurrency, extensions как pg_trgm для fuzzy search) и MySQL, объясню нюансы (case-sensitivity, NULL handling), edge-кейсы и интеграцию с Go (database/sql + pgx), фокусируясь на security (prepared statements), performance (EXPLAIN, indexes) и extensibility (e.g., с GROUP BY для breakdowns). Это обеспечивает efficiency и accuracy, избегая common pitfalls вроде case mismatches в русских именах.
1. Базовый SQL-запрос
Используем COUNT(*) — оно считает все rows (включая с NULL в других cols), быстрее COUNT(1) или COUNT(фамилия) на large tables. WHERE фильтрует exactly "Иванов".
Для PostgreSQL (double quotes для русских колонок):
-- Базовый подсчет
SELECT COUNT(*) AS ivanov_count
FROM clients
WHERE "фамилия" = 'Иванов';
- Результат: Single row с column ivanov_count (e.g., 5).
- Объяснение: = для exact match (case-sensitive по умолчанию в Postgres). Если 0 — нет таких клиентов. Performance: Full table scan без index; <1ms с B-tree на фамилия.
Для MySQL (backticks):
SELECT COUNT(*) AS ivanov_count
FROM clients
WHERE `фамилия` = 'Иванов';
2. Улучшенные варианты
-
Case-insensitive поиск: Русские фамилии могут варьироваться (Иванов vs иванов). Используйте ILIKE (Postgres) или LOWER.
PostgreSQL:
SELECT COUNT(*) AS ivanov_count
FROM clients
WHERE LOWER("фамилия") = LOWER('Иванов'); -- Или ILIKE 'Иванов' (pattern match)- ILIKE:
WHERE "фамилия" ILIKE 'Иванов';— ignores case, но exact (no wildcards). Для fuzzy:%Иванов%с trigram index.
MySQL (collation utf8_general_ci для case-insens):
SELECT COUNT(*) AS ivanov_count
FROM clients
WHERE LOWER(`фамилия`) = 'иванов'; - ILIKE:
-
Исключение NULL: Если фамилия может быть NULL (хотя NOT NULL лучше), добавьте IS NOT NULL.
SELECT COUNT(*) AS ivanov_count
FROM clients
WHERE "фамилия" IS NOT NULL AND "фамилия" = 'Иванов'; -
С GROUP BY (если breakdown): Как упомянул собеседник — для группировки, e.g., по региону (если column регион).
PostgreSQL:
SELECT
"регион", -- Доп. column, если есть
COUNT(*) AS ivanov_count
FROM clients
WHERE "фамилия" ILIKE 'Иванов'
GROUP BY "регион"
HAVING COUNT(*) > 1 -- Только группы >1
ORDER BY ivanov_count DESC;- Результат: Регион | ivanov_count (e.g., Москва | 3). HAVING фильтрует aggregates.
-
С фильтрами (e.g., старше 30, из prev questions):
SELECT COUNT(*) AS adult_ivanov_count
FROM clients
WHERE "фамилия" = 'Иванов'
AND AGE(CURRENT_DATE, "дата_рождения") > INTERVAL '30 years';
3. Edge-кейсы и оптимизация
- Case/Encoding: В UTF-8 "Иванов" ≠ "иванов" — используйте ILIKE/LOWER. Для typos: pg_trgm extension:
CREATE EXTENSION pg_trgm; CREATE INDEX idx_surname_trgm ON clients USING GIN ("фамилия" gin_trgm_ops);— ускоряет ILIKE '%Иванов%'. - NULL/Empty: COUNT(*) игнорирует NULL в WHERE; тест: INSERT ('', ...) — не match.
- Large data: На 10M rows — index essential:
CREATE INDEX idx_clients_surname ON clients ("фамилия");(B-tree для =; GIN для ILIKE). EXPLAIN: Index Scan (good) vs Seq Scan (bad, optimize!). - Concurrency: В Go apps — read replicas для SELECTs, чтобы не lock writes.
- Тестирование: Sample: INSERT 5x ('Иванов', ...); Run query — assert 5. Edge: 0 inserts — 0.
4. Интеграция с Golang: Выполнение в API
В Go app (e.g., Gin handler /api/ivanov-count) используйте sql.DB с prepared query (даже без params — для consistency). Return JSON {"ivanov_count": 5}. Для Postgres — pgx для streaming large results, но здесь scalar.
package main
import (
"database/sql"
"encoding/json"
"fmt"
"log"
"net/http"
_ "github.com/lib/pq" // Postgres driver
)
type IvanovCount struct {
Count int `json:"ivanov_count"`
}
func ivanovCountHandler(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Prepared для reuse; case-insensitive variant
query := `
SELECT COUNT(*) AS count
FROM clients
WHERE LOWER("фамилия") = LOWER('Иванов');
`
var count int
err := db.QueryRow(query).Scan(&count)
if err != nil {
log.Printf("Query error: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
result := IvanovCount{Count: count}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(result); err != nil {
log.Printf("JSON encode error: %v", err)
http.Error(w, "Response encoding failed", http.StatusInternalServerError)
}
}
}
func main() {
db, err := sql.Open("postgres", "host=localhost port=5432 user=postgres password=pass dbname=crm sslmode=disable")
if err != nil {
log.Fatal("DB connection error:", err)
}
defer db.Close()
if err := db.Ping(); err != nil {
log.Fatal("DB ping error:", err)
}
http.HandleFunc("/api/ivanov-count", ivanovCountHandler(db))
fmt.Println("Server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
- Объяснение: QueryRow + Scan для single value — efficient (no rows loop). LOWER для case-insens. Errors: Log, 500 (не expose SQL err). JSON: Simple struct. Для GROUP BY — return []struct{Region string; Count int}.
- Prepared statement: db.Prepare(query) + stmt.QueryRow() для high-traffic (reuse plan).
- Testing: Unit с sqlmock: Mock row.Scan(5), assert result.Count==5. Integration: Docker Postgres, insert data, curl /api/ivanov-count — {"ivanov_count":5}.
- Advanced: Context: db.QueryRowContext(r.Context(), query) для timeout. Cache: If called often, use Redis (SETEX "ivanov_count" 300, count). Metrics: Prometheus для query latency.
5. Дополнительные соображения
- Security: Prepared — no injection (хотя literal 'Иванов' safe). Если param (e.g., ?surname=), 1: WHERE "фамилия" = 1.
- Масштабирование: На big data — materialized view:
CREATE MATERIALIZED VIEW ivanov_stats AS SELECT COUNT(*) ...; REFRESH MATERIALIZED VIEW ...;(Postgres). В Go: Background job для refresh. - Различия DBMS: Postgres ILIKE native; MySQL BINARY для case-sens. Collation: UTF8 для кириллицы.
- Расширение: Для top surnames:
SELECT "фамилия", COUNT(*) FROM clients GROUP BY "фамилия" ORDER BY COUNT(*) DESC LIMIT 10;.
Этот запрос и реализация — scalable foundation для analytics в Go apps. В production мониторьте с pg_stat_statements (slow queries) и всегда index на filter cols. Для fuzzy search добавьте full-text (tsvector в Postgres).
Разница между severity и priority в управлении дефектами
В тестировании и разработке ПО severity (серьёзность) описывает потенциальный вред дефекта для системы или пользователей: от cosmetic (низкая, e.g., опечатка в UI) до blocking (высокая, e.g., crash приложения или потеря данных, что делает feature unusable). Priority (приоритет), напротив, определяет urgency фикса на основе бизнес-контекста: release deadlines, impact на revenue, наличие workarounds или зависимостей. Severity — объективная оценка риска (часто по шкале 1–5, где 5 = critical/blocking), priority — субъективная, зависит от PM/PO и roadmap (high/medium/low).
Да, дефект с blocking severity и low priority может существовать, особенно в agile/iterative проектах (e.g., на early stages MVP). Здесь high severity сигнализирует о фундаментальной проблеме, но low priority отражает, что фикс не critical для immediate delivery, так как риски минимизированы (no prod users, workaround exists, или focus на core features). В production такой дефект стал бы high priority, но в dev/staging — deferred на backlog. Это требует careful triage в tools вроде Jira (fields: Severity=Blocker, Priority=Low), чтобы избежать tech debt accumulation. В Go-проектах (API с DB) такие баги часто в edge-кейсах concurrency или data handling, где severity high из-за data integrity, но priority low, если не affects main flow.
Пример: Data overwrite в concurrent updates Go API с SQL
Представьте early-stage проект — банковский микросервис на Golang (Gin framework + PostgreSQL), где API /api/account/balance позволяет обновлять баланс аккаунта (POST с JSON {"amount": 100}). Feature в alpha: нет real users, только internal testing, deploy planned через 3 месяца. Дефективный handler использует naive SQL UPDATE без locking, приводя к lost updates в concurrent scenarios (e.g., два запроса +100 одновременно — итог +100 вместо +200).
Severity: Blocking (high/critical)
- Impact: Data corruption — баланс overwrite'ится, приводя к financial loss (e.g., user переводит 200, но видит 100). В prod это могло бы нарушить compliance (e.g., PCI DSS), вызвать legal issues или total trust loss. Баг "блокирует" reliability data layer, делая весь service unusable для transactions. Без фикса — potential cascade failures (e.g., negative balance triggers alerts, но wrong data propagates to reports).
Priority: Low
- Context: Ранний этап (sprint 2/10), no prod traffic, workaround — manual DB checks в testing (e.g., run updates sequentially). Business focus на core auth/login, не на transactions (MVP scope: read-only balances). Фикс требует refactor (add mutex или DB-level advisory locks), что delays features на 1–2 дня — low priority, scheduled на refinement phase. Если есть hotfix branch, defer to post-MVP.
Код примера (дефективный handler в Go):
package main
import (
"database/sql"
"encoding/json"
"log"
"net/http"
"github.com/gin-gonic/gin"
_ "github.com/lib/pq"
)
type BalanceUpdate struct {
Amount float64 `json:"amount"`
}
func updateBalanceHandler(db *sql.DB) gin.HandlerFunc {
return func(c *gin.Context) {
var update BalanceUpdate
if err := c.ShouldBindJSON(&update); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid input"})
return
}
accountID := c.Param("id")
// Naive UPDATE — no locking, concurrent calls overwrite
result, err := db.Exec(`
UPDATE accounts
SET balance = balance + $1
WHERE account_id = $2
`, update.Amount, accountID)
if err != nil {
log.Printf("DB error: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Update failed"})
return
}
rowsAffected, _ := result.RowsAffected()
if rowsAffected == 0 {
c.JSON(http.StatusNotFound, gin.H{"error": "Account not found"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "Balance updated"})
}
}
func main() {
db, err := sql.Open("postgres", "host=localhost port=5432 user=postgres password=pass dbname=bank sslmode=disable")
if err != nil {
log.Fatal(err)
}
defer db.Close()
r := gin.Default()
r.POST("/api/account/:id/balance", updateBalanceHandler(db))
r.Run(":8080")
}
Как проявляется дефект:
- Тест: Запустить два concurrent POST (e.g., via Apache Bench или Go routine): Каждый +100 на initial balance=0. Ожидаемо: 200. Фактически: 100 (второй UPDATE reads old value).
- В SQL logs (pg_stat_activity): Два UPDATE видны, но no isolation — lost update.
- Repro: Sample DB — INSERT INTO accounts (account_id, balance) VALUES (1, 0); curl -X POST twice.
Почему low priority на early stage:
- No users: Internal QA симулирует single-threaded, баг не blocks demo. Workaround: Add app-level queue (e.g., Redis) для serialize updates — temporary patch без deep refactor.
- Business: PM говорит "Fix concurrency post-beta, prioritize UI". В roadmap: P3 (low), assigned to next sprint после core.
- Risks: Если ignore, tech debt grows — в prod (high traffic) → outages. Mitigation: Unit tests с race detector (go test -race), integration с sqlmock для concurrent mocks.
Фикс (кратко, для completeness):
Используйте SELECT FOR UPDATE (pessimistic locking) или optimistic с version (CAS):
// В handler
tx, err := db.Begin()
if err != nil { /* handle */ }
defer tx.Rollback()
var currentBalance float64
err = tx.QueryRow("SELECT balance FROM accounts WHERE account_id = $1 FOR UPDATE", accountID).Scan(¤tBalance)
if err != nil { /* not found */ }
// Calculate new: newBalance = currentBalance + update.Amount
_, err = tx.Exec("UPDATE accounts SET balance = $1 WHERE account_id = $2", newBalance, accountID)
if err != nil { /* handle */ }
tx.Commit()
c.JSON(http.StatusOK, gin.H{"new_balance": newBalance})
Это blocks concurrent access, ensuring atomicity. Альтернатива: App-level mutex (sync.RWMutex) для in-memory, но DB-level better для distributed.
Обработка таких дефектов в команде
- Triage process: В daily standup или bug bash — assess severity (auto-blocker via CI: fail build on race), priority via MoSCoW (Must/Should). Tools: Jira/Greenhopper — label "tech-debt", estimate 5 story points.
- Mitigation: Regression tests (Go: table-driven с concurrent goroutines), monitoring (Prometheus для update latency, alerts на anomalies). В CI: go test -race как gate.
- Lessons: В mature проектах (post-MVP) такой баг — high priority (P0). Early detection via code reviews/chaos testing (e.g., Gremlin для simulate concurrency) prevents escalation. Для Go devs: Всегда think concurrency-first (channels/mutexes), особенно с SQL (use transactions).
Этот сценарий подчёркивает, почему severity/priority decoupled: Позволяет balance speed-to-market с quality, но требует vigilance, чтобы low-priority не стал blocker в prod. В реальном проекте документируйте в post-mortem: "Deferred data race — cost 2 days refactor later".
Вопрос 11. Какие виды серьезности дефектов бывают?
Таймкод: 00:29:32
Ответ собеседника: правильный. Блокирующий (выше critical), critical, major, medium, minor, low - от наивысшей к низкой.
Правильный ответ:
Серьёзность (severity) дефекта — это объективная мера потенциального вреда, который он наносит системе, пользователям или бизнесу, оцениваемая по шкале от минимального (cosmetic issues) до максимального (system crash или data loss). В отличие от приоритета (который зависит от urgency и бизнес-контекста), severity фокусируется на техническом и функциональном impact: насколько дефект нарушает expected behavior, влияет на safety, security или compliance. Стандартные уровни (основанные на ISTQB, IEEE или практиках в tools вроде Jira/Bugzilla) обычно включают 5–6 градаций, с Blocker как highest (более critical, чем Critical). В реальных проектах (e.g., Golang API с PostgreSQL backend) severity присваивается QA/dev во время triage: high severity triggers immediate review, но low может быть deferred. Это помогает prioritize fixes в sprints, минимизируя risks вроде production outages. Я разберу уровни с примерами из веб/API-тестирования (включая Go handlers и SQL queries), criteria для classification, и как это интегрируется в workflow, чтобы подчеркнуть, почему accurate severity assessment снижает MTTR (mean time to resolution) на 30–50% в agile teams.
Шкала seriousness: От highest к lowest
Уровни варьируются по компаниям (e.g., Google: S0–S5; Microsoft: Critical–Low), но common framework — следующий (от наивысшей к низкой):
-
Blocker / Blocking (S0 или Level 5: Полная блокировка)
Дефекты, которые полностью останавливают разработку, тестирование или использование системы. Нет workaround, feature unusable. Impact: Zero progress в affected area, potential halt release.- Criteria: System crash на startup, infinite loop, total data loss без recovery. В prod — immediate hotfix.
- Пример в Go API: В handler /api/login concurrent goroutines вызывают deadlock (sync.Mutex не released на panic), app hangs на всех requests. Тест: Load test с 10 users — server unresponsive. Severity high, потому что blocks auth flow (core feature). Фикс: Recovery middleware с defer unlock.
- В SQL контексте: Corrupted index на primary table (e.g., clients), все SELECT/INSERT fail с "table corrupted". EXPLAIN показывает invalid plan.
- Workflow: Auto-block CI/CD (e.g., go test -race fails build), notify leads via Slack. В Jira: Severity=Blocker, auto-escalate.
-
Critical (S1 или Level 4: Высокий вред)
Дефекты, вызывающие крах системы или потерю данных, но с partial workaround (e.g., restart). Impact: Major functionality broken, security breach, financial loss.- Criteria: Crash под нагрузкой, unauthorized access, data corruption (e.g., wrong calculations). Не blocks весь system, но critical paths.
- Пример: В Go SQL handler UPDATE accounts SET balance = -100 (negative allowed, но business rule forbids) — leads to overdraft fraud. Тест: Concurrent transactions, verify balance via SELECT — corruption. Или SQL injection в unprepared query:
db.Exec("UPDATE users SET password = '" + input + "'")— attacker changes all passwords. Severity critical из-за compliance risks (GDPR/PCI). - Фронт пример: JS fetch to Go API returns 500, но no error UI — user data lost silently.
- Workflow: High visibility (red in dashboards), fix в current sprint. Tools: Sentry для crash grouping.
-
Major (S2 или Level 3: Значительное нарушение)
Дефекты, сильно влияющие на usability или performance, без краха. Impact: Key features degraded, но system runs. Workaround partial.- Criteria: Wrong output (e.g., 80% data missing), slow response (>5s), broken integration. Не security, но affects user experience.
- Пример: В Go API JOIN query на clients и accounts returns duplicates (no DISTINCT), report shows inflated counts. Тест: Sample data (1 client, 3 accounts) — output 3x wrong. Или SQL slow query (no index on фамилия): SELECT COUNT(*) WHERE фамилия='Иванов' takes 10s на 1M rows. Severity major, так как blocks analytics dashboard. Фикс: ADD INDEX, rewrite с EXPLAIN.
- Фронт: Button "Save" works, но не validates input — partial form submit, data inconsistency.
- Workflow: Assign to next sprint, estimate 3–5 points. Monitor с Prometheus (query latency alerts).
-
Medium / Minor (S3 или Level 2: Умеренное нарушение)
Дефекты, влияющие на non-core features или edge-кейсы. Impact: Annoying, но не critical; full workaround exists.- Criteria: UI glitches, minor inaccuracies (<10% data wrong), non-critical errors (e.g., log missing). Не affects main flows.
- Пример: В Go handler /api/clients/older-30 AGE calculation wrong для leap years (e.g., Feb 29 birth — age off by 1), но 95% correct. Тест: Specific date input, assert output. Или SQL WHERE "дата_рождения" <= fixed_date — brittle, fails yearly. Severity medium, так как workaround: Manual calc.
- Фронт: CSS mismatch (button color wrong per Figma), но functional.
- Workflow: Backlog, low effort (1 point). Batch fixes в refinement.
-
Low / Trivial / Cosmetic (S4/S5 или Level 1: Минимальный вред)
Дефекты, не влияющие на functionality — визуальные, docs или preferences. Impact: None или aesthetic.- Criteria: Typos в UI, alignment off, optional features broken. No user harm.
- Пример: В Go JSON response "surname" вместо "фамилия" (key mismatch, но parseable), или SQL comment // wrong — no runtime effect. Тест: curl, verify schema. Фронт: Tooltip text error ("Сохранить" как "Save").
- Workflow: Defer to cleanup sprint, self-assign junior. В Jira: Severity=Low, close after ACK.
Классификация и best practices
- Как определять severity: Используйте matrix: Impact (users affected) x Likelihood (frequency). E.g., data loss for 1% users = Critical; для 0.01% = Medium. Tools: Jira custom fields (dropdown), auto-calc по keywords (crash=Critical). В Go: Integrate с testing.T — fail on high severity (e.g., assert no panics).
- Связь с priority: Severity guides, priority decides (e.g., Blocker severity + low prod impact = Medium priority). Избегайте mismatch: High severity low priority — tech debt risk (как в prev вопросе).
- Примеры в Go/SQL проекте:
- Blocker: Go race condition в DB pool (sql.DB.SetMaxOpenConns=0) — no connections, all queries hang.
- Critical: SQL deadlock в transaction (no COMMIT) — stuck updates.
- Major: Go middleware skips auth для /admin — partial security hole.
- Medium: SQL ORDER BY missing — random output.
- Low: Log level INFO вместо DEBUG в prod config.
- Workflow integration: В CI/CD (GitHub Actions): Run tests, classify via scripts (e.g., grep errors → Critical). Reporting: Dashboards (Grafana) по severity trends. Post-mortem: Analyze high severity spikes (e.g., deploy introduced race — add -race flag). В команде: QA assigns initial, dev confirms/negotiates.
- Для senior dev: Severity assessment — skill для risk management. В interviews: Приведите example с metrics (e.g., "Fixed Critical SQL injection — prevented 1000+ breaches"). Tools: ELK для log-based severity (parse ERROR=Critical).
Эта шкала обеспечивает consistent triage, помогая фокусироваться на high-impact bugs, пока low не накапливаются. В Go-проектах всегда log severity в Zap (zap.String("severity", "critical")), чтобы мониторить в Kibana. Для практики: В вашем тестовом suite classify 10 bugs по уровням — это sharpens judgment.
Вопрос 12. В чем отличие блокирующего дефекта от критического?
Таймкод: 00:29:57
Ответ собеседника: неполный. Блокирующий полностью останавливает работу (краш приложения), критический позволяет работать, но не так, как нужно, с большой ошибкой.
Правильный ответ:
Отличие между блокирующим (blocker) и критическим (critical) дефектом лежит в степени воздействия на систему и возможность продолжения работы: блокирующий дефект полностью парализует процесс (разработки, тестирования или использования), делая feature или весь сервис недоступным без workaround, в то время как критический позволяет частично функционировать, но с значительными рисками (e.g., data loss, security breach), где workaround существует, хоть и неудобный или временный. Оба — high severity уровни (S0/S1 в шкалах вроде Jira), но blocker требует immediate intervention (hotfix или rollback), так как zero progress possible, а critical — urgent fix в текущем цикле, с monitoring для containment. В контексте Golang-разработки (API с SQL-backend) blocker часто в core infrastructure (e.g., connection pool exhaustion), critical — в business logic (e.g., wrong calculations). Это различие критично для triage: blocker escalates автоматически (e.g., P0 priority), critical — с assessment impact (affected users/revenue). В production такие дефекты мониторятся APM (Datadog/New Relic), с alerts на crash rates >1%, чтобы минимизировать downtime. Я разберу различия по criteria, примерам и workflow, с фокусом на Go/SQL scenarios, чтобы показать, как распознавать и handle их для robust systems — это ключевой skill для senior dev, где timely classification снижает outage costs на 40–60%.
Ключевые criteria отличия
-
Impact на доступность и functionality:
- Blocker: Total failure — система "заморожена", no alternative paths. Нет даже partial usage; blocks entire pipeline (e.g., CI/CD halt). Likelihood: Rare, но catastrophic (e.g., 100% users affected).
- Critical: Severe degradation — core features broken, но secondary paths работают. Partial workaround (e.g., manual intervention) possible, но не scalable. Likelihood: Higher, affects 50–90% scenarios.
-
Workaround и recovery:
- Blocker: Нет viable workaround; recovery только через code change/restart (e.g., kill process). Время до фикса: <1 hour, иначе prod outage.
- Critical: Workaround exists (e.g., disable feature via config), но painful (manual DB fixes). Recovery: 1–4 hours, с temporary mitigation (e.g., feature flag).
-
Business/Technical risks:
- Blocker: Immediate safety/security halt (e.g., app crash на login — no access). Tech: Infrastructure failure (e.g., DB deadlock locking all tx).
- Critical: High risk escalation (e.g., data corruption propagating). Tech: Logic errors (e.g., auth bypass для 80% endpoints).
-
Detection и metrics:
- Blocker: Detected instantly (e.g., 500 errors on all requests, zero throughput). Metrics: Availability 0%, error rate 100%.
- Critical: Detected via tests/logs (e.g., 500 on specific paths, throughput 20%). Metrics: Availability <50%, error rate 20–80%.
В tools (Jira/Azure DevOps): Blocker — red icon, auto-notify leads; Critical — orange, с SLA <24h.
Примеры из практики (Go API с PostgreSQL)
В реальном проекте (e.g., e-commerce service: Users API для carts/orders) различия проявляются в concurrency или data handling — Go сильна в parallel, но race conditions/SQL locks могут escalate.
-
Blocker пример: Deadlock в DB connection pool (total stop)
В Go handler /api/order/create: Используете sql.DB с SetMaxOpenConns=5, но concurrent goroutines (50 users) exhaust pool — все queries hang indefinitely (no timeout). App unresponsive: Nginx queues requests, leading to 5xx on all endpoints.- Почему blocker: Нет workaround — restart не помогает под load (race recreates). Blocks entire service (no orders, no reads). Тест: Load test (wrk -c50) — 0 RPS, logs: "context deadline exceeded" everywhere.
- Go код snippet (дефект):
func createOrderHandler(db *sql.DB) gin.HandlerFunc {
return func(c *gin.Context) {
// No pool management; concurrent calls >5 hang
tx, err := db.Begin() // Blocks if pool full
if err != nil {
c.JSON(500, gin.H{"error": "DB unavailable"})
return
}
defer tx.Rollback() // But if hang, no defer
_, err = tx.Exec("INSERT INTO orders ...") // SQL hangs
// ...
tx.Commit()
}
} - Фикс: db.SetMaxOpenConns(20), context.WithTimeout(5s) на Query, circuit breaker (Hystrix-go). Impact: Prod downtime 30min, revenue loss $10k.
-
Critical пример: Lost update в balance calculation (partial function, data corruption)
В /api/account/transfer: Naive UPDATE balance = balance - amount без SELECT FOR UPDATE — concurrent transfers (A→B + B→A) result in negative/wrong balance, но app continues (other endpoints work). Workaround: Manual DB correction via pgAdmin.- Почему critical, не blocker: System runs (reads OK, non-concurrent tx pass), но 20% transfers corrupt data (e.g., overdraft). Partial: Disable transfers via flag, use single-thread queue. Тест: Go routines simulate 10 concurrent, assert balance==expected — fails intermittently.
- Go/SQL код snippet (дефект):
-- В Go: db.Exec("UPDATE accounts SET balance = balance - $1 WHERE id = $2", amount, fromID)
-- Concurrent: Reads old balance, overwrites// Handler
_, err := db.Exec("UPDATE accounts SET balance = balance - $1 WHERE id = $2", amount, fromID)
if err == nil {
// No verification; continues, but data wrong
c.JSON(200, gin.H{"success": true})
} - Фикс: Pessimistic locking:
SELECT balance FROM accounts WHERE id = $1 FOR UPDATE, calc new, UPDATE. Или optimistic: Add version column, CAS. Impact: Silent fraud risk, detected via audit logs (Kibana query: "balance < 0").
Разница в действии: Blocker — stop everything (e.g., kill goroutines, rollback deploy). Critical — contain (e.g., rate limit endpoint, notify users via email).
Workflow обработки в команде
- Triage: QA detects (e.g., via Cypress for UI crash, go test -race for concurrency). Dev confirms: Blocker — emergency call; Critical — ticket с repro steps (e.g., curl script + DB dump).
- Escalation: Blocker — PagerDuty alert, war room (Slack #incident). Critical — Jira epic, estimate 8 points, fix в hotfix branch.
- Prevention:
- Go: Always -race flag в CI, mutexes/channels для sync, sql.Tx с isolation level.
- SQL: EXPLAIN для locks, pg_locks view monitoring. Tools: pprof для deadlocks, New Relic для tx traces.
- Testing: Chaos engineering (e.g., inject concurrency via litmus), load tests (k6) для simulate.
- Metrics и lessons: Track в dashboards (Grafana: blocker incidents/mo). Post-mortem: "Blocker from pool misconfig — added auto-scale". В Go projects: Use structured logs (Zap: zap.String("defect_type", "blocker")) для Kibana analysis.
Это отличие подчёркивает layered defense: Blocker — firewall (stop at gate), Critical — alarm (alert and mitigate). В senior роли фокусируйтесь на root cause (e.g., 5 Whys: Why deadlock? Pool too small → Why? No monitoring). Для интервью: Приведите personal example — "Handled blocker DB hang by adding timeouts, reduced MTTR from 2h to 15min".
