Чому я пишу тести на останніх, і чому це не те, що ви думаєте це означає
**Суперечний вибір:**Тестовий розвиток - це блискуча практика, яка вирішує невірну проблему для більшості творчих робіт.
Три фази будівельного програмного забезпечення (насправді це працює)
Ось так я створюю програмне забезпечення.
Це просто, це ефективно, і це змушує ТДД пундиків ввічливо борознити їхні брови:
Зроби це працювати.*Зробимо її красивою.*Заблокуй його тестами.
graph TB
Start[New Feature Request] --> P1[Phase 1: Make It Work]
P1 --> P1_Private[Private Exploration]
P1_Private --> P1_Sketch["Sketch the solution<br/>Messy code OK<br/>No tests yet<br/>Focus on learning"]
P1_Sketch --> P1_Run["Run it manually<br/>Try different inputs<br/>See what breaks<br/>Understand the problem"]
P1_Run --> P1_Decision{Does it work<br/>basically?}
P1_Decision -->|No| P1_Iterate[Try different approach]
P1_Iterate --> P1_Sketch
P1_Decision -->|Yes| P2
P2[Phase 2: Make It Pretty] --> P2_Refine[Private Refinement]
P2_Refine --> P2_Clean["Clean up the code<br/>Better names<br/>Type hints/annotations<br/>Clear structure"]
P2_Clean --> P2_Align["Align with spec<br/>Cover all requirements<br/>Handle edge cases<br/>Polish API surface"]
P2_Align --> P2_Optimise["Optimise<br/>Performance<br/>Readability<br/>Maintainability"]
P2_Optimise --> P3
P3[Phase 3: Lock It Down] --> P3_Share[Public Sharing]
P3_Share --> P3_Tests["Write comprehensive tests<br/>Full coverage<br/>Edge cases<br/>Error conditions"]
P3_Tests --> P3_CI["CI/CD Integration<br/>Automated testing<br/>Code review<br/>Documentation"]
P3_CI --> P3_Deploy["Ready to Share<br/>Commit to main<br/>Deploy to prod<br/>Other devs can use it"]
P3_Deploy --> Done[Well-Tested,<br/>Well-Designed Code]
style P1 stroke:#ff6b6b,stroke-width:3px
style P2 stroke:#4ecdc4,stroke-width:3px
style P3 stroke:#95e1d3,stroke-width:3px
style Done stroke:#38ada9,stroke-width:4px
У цьому порядку.*Завжди.*Не тому, що я ледачий.
Не тому, що я не ціную тестування.
Ось як насправді відбувається творча робота.
Коли ви досліджуєте проблемний простір, ви ще не повністю розумієте.
Коли ви досліджуєте проблемний простір, ви ще не повністю розумієте.
Дозвольте розповісти.
Це заплутана частина.Часть, где я понятия не имею, что делаю.
Я намагаюся зрозуміти:
Чи цей API дійсно працює так, як стверджують докторини?
Чи можу я взагалі вирішити цю проблему за допомогою інструментів, які у мене є?
Що означає "правильне" навіть у цьому контексті?*Це двогодинна проблема чи двотижнева проблема?*Це винахід.
Це дослідження.
Це джаз.
# Ugly, exploratory code
def process_thing(data):
# TODO: This is terrible, fix later
result = []
for item in data:
# Not sure if this is the right approach...
x = item.get('value')
if x: # Is this even the right condition?
result.append(x * 2) # Why multiply by 2? Testing something...
return result
І знаєте, що вбиває джаз?**Кто-то стоял над твоим плечо и говорил "Спершу напишите тест."**Чому досі немає випробувань?
Тому що*форма до сих пор жидкость.*Уявіть, що ви скульптор.
У вас є блок мармуру і туманна ідея: "Я хочу вирізати птаха."
Визначте його розмах крил.Вкажіть кут кожної пір'їни.
Тепер напишіть на ці специфікації."*Це... не те, як вирізали птахів (Або програмне забезпечення пишеться, як виявляється.)*Справжні скульптори
спочатку перекрий форму.Вони малюють.
Вони роблять макет.*Щоб знайти загальну форму, вони відрізають великі брили каменю.*Тільки пізніше вони уточнюють деталі.
Код такий самий.
Цей код
рядовий.
Це розмова сама з собою.
Саме так я розумію, що таке "працездатність."
активно шкідлива для здоров'я.
Тому що кожного разу, коли я розумію "очекайте, цей підхід хибний," мені також доведеться переписати тести.
Це не суворо.
Свобода швидко зазнає невдачі
# Before (Phase 1) - Exploratory code
def process_thing(data):
result = []
for item in data:
x = item.get('value')
if x:
result.append(x * 2)
return result
# After (Phase 2) - Refined code
def extract_doubled_values(items: list[dict]) -> list[int]:
"""Extract 'value' field from items and double each one.
Only includes items where 'value' is present and truthy.
"""
return [
item['value'] * 2
for item in items
if item.get('value')
]
Про фазу 1
// Before (Phase 1) - Exploratory code
public List<int> ProcessThing(List<Dictionary<string, object>> data)
{
var result = new List<int>();
foreach (var item in data)
{
if (item.ContainsKey("value"))
{
var x = item["value"];
if (x != null)
{
result.Add(Convert.ToInt32(x) * 2);
}
}
}
return result;
}
// After (Phase 2) - Refined code
/// <summary>
/// Extracts 'value' field from items and doubles each one.
/// Only includes items where 'value' is present and non-null.
/// </summary>
public IEnumerable<int> ExtractDoubledValues(IEnumerable<IDictionary<string, object>> items)
{
return items
.Where(item => item.ContainsKey("value") && item["value"] != null)
.Select(item => Convert.ToInt32(item["value"]) * 2);
}
швидкість навчання.
Мне нужно выяснить, что библиотека третьей части, которую я хотел использовать... не такая, как объявление.*Я маю зрозуміти, що проблема, яку я вирішую, це не та проблема, яку я маю вирішити.маєрешать.*Тесты уповільнюют это.
Не тому, що тестування є повільним, а тому, що
Тогда я эмоционально инвестирована в не то, что нужно.
Краще швидко, на самоті, без тестів, щоб підтримувати і без его, щоб захистити.Що означає " праця " в фазі 1Це означає: "Я її проїхав.
Я дал ему некоторые входы.
# Phase 1: Works, but awkward to use
process_thing(data)
# Phase 2: Clear, flexible, composable
extract_doubled_values(
items,
scale_factor=2.0,
include_zeros=False
)
Він видавався досить розумним.
// Phase 1: Works, but awkward to use
ProcessThing(data);
// Phase 2: Clear, flexible, composable
ExtractDoubledValues(
items,
scaleFactor: 2.0,
includeZeros: false
);
// Or even better with a fluent API
items.ExtractValues()
.Scale(by: 2.0)
.ExcludingZeros()
.ToList();
Я в 60% впевнений, що розумію цю проблему зараз."
Ось так.**Никаких мерзавых дел.**Без обробки помилок.
graph LR
subgraph "Phase 2 Refinement Process"
A[Rough Code] --> B[Clean Structure]
B --> C[Add Types]
C --> D[Polish API]
D --> E[Static Analysis]
E --> F{Quality Gates Pass?}
F -->|No| G[Fix Issues]
G --> B
F -->|Yes| H[Ready for Phase 3]
end
style A stroke:#ff6b6b,stroke-width:3px
style H stroke:#95e1d3,stroke-width:3px
*Просто шип.*Доказ концепції.
Малюнок.Фаза 2: зробити її пристойною (удосконалення)
Гаразд, тепер я розумію проблему.У мене є щось, що працює, принаймні для щасливого шляху.
Тепер я можу почати турбуватися про якість.
Приклад Python:*Приклад C#:*Краще ім'я.
Надрукувати анотації.*Документація XML.*Реалізація чистішого LINQ.
Вирівняти за спектром
Тепер, коли я знаю, що я насправді будую, я повертаюся до початкових вимог і впевнюся, що я вирішую
праворуч
апроблема.
Часто це виявляє прогалини:
"О, вони хотіли інакше впоратися з від'ємними числами."
досвід.**Python:**C#:
Лучше название.
Чому досі немає випробувань?
def test_extract_doubled_values_basic():
items = [{'value': 5}, {'value': 10}]
assert extract_doubled_values(items) == [10, 20]
def test_extract_doubled_values_skips_missing_values():
items = [{'value': 5}, {'name': 'foo'}, {'value': 3}]
assert extract_doubled_values(items) == [10, 6]
def test_extract_doubled_values_handles_zeros():
items = [{'value': 0}, {'value': 5}]
# By default, zeros are excluded (falsy)
assert extract_doubled_values(items) == [10]
def test_extract_doubled_values_includes_zeros_when_configured():
items = [{'value': 0}, {'value': 5}]
assert extract_doubled_values(items, include_zeros=True) == [0, 10]
def test_extract_doubled_values_custom_scale_factor():
items = [{'value': 5}]
assert extract_doubled_values(items, scale_factor=3.0) == [15.0]
def test_extract_doubled_values_rejects_negative_scale_factor():
with pytest.raises(ValueError):
extract_doubled_values([], scale_factor=-1.0)
def test_extract_doubled_values_handles_empty_list():
assert extract_doubled_values([]) == []
def test_extract_doubled_values_handles_non_numeric_values():
items = [{'value': 'not a number'}]
with pytest.raises(TypeError):
extract_doubled_values(items)
О, я постоянно его проверяю.
У меня открытый фантастик.
Тому що
# This comment will drift from reality:
# Doubles all numeric values, skipping None
# This test will fail if reality changes:
def test_extract_doubled_values_skips_none_values():
items = [{'value': None}, {'value': 5}]
assert extract_doubled_values(items) == [10]
Я до сих пор выявляю, что стоит проверки.
У фазі 1, я не знав, чи існує ця функція.У другій фазі я знаходжу:"О, коефіцієнт масштабу не може бути негативним.
"Якщо список порожній, ми повинні повернути порожній, а не None.""Ми повинні з приємністю ставитися до нецифрічних цінностей."
Ці розуміння
виходить з реорганізації.
Вони не є очевидними на передньому плані.
Код чистий.*API має сенс.*Я раскрою дела.
Тепер я пишу тести.
Багато тестів.
У якості тестів на повну роботу.Чому зараз?
Тому що
тести - це механізм спільного використання.Вони більше не для мене.
Я вже розумію цей код тісно.
Канальна лінія CI
(що має запобігти регресії)
Майбутні супровідники
Тести є тестамишар презентації
для моєї реалізації.Они документируют:
Що робить цей кодЯкі вхідні дані є коректнимиЯкі вихідні дані слід очікувати
Які припущення я роблю
Повна обкладинка без компромісів
Змінити цю функцію з упевненістю
Тести як документація
Документація програми.
graph TB
subgraph "Traditional TDD (Red-Green-Refactor)"
TDD1[Write Failing Test] --> TDD2[Write Minimal Code to Pass]
TDD2 --> TDD3[Refactor]
TDD3 --> TDD1
end
subgraph "Three-Phase Approach (Explore-Refine-Lock)"
P1[Explore Solution Space] --> P2[Refine Implementation]
P2 --> P3[Write Comprehensive Tests]
P3 --> P4[Share With Team]
end
style TDD1 stroke:#ffaa00,stroke-width:2px
style TDD2 stroke:#ffaa00,stroke-width:2px
style TDD3 stroke:#ffaa00,stroke-width:2px
style P1 stroke:#ff6b6b,stroke-width:3px
style P2 stroke:#4ecdc4,stroke-width:3px
style P3 stroke:#95e1d3,stroke-width:3px
**Но я также не пишу тесты, пока код не готов делиться.**Фази такі:
Особисте дослідження(Лише я, без тестів)
Особисте вдосконалення
(Тільки я, все одно ніяких тестів) |-----|-------------| Публічний спільний доступ (Перевірка коду, аналіз коду, всі дев'ять ярдів) Це захищає обидва мої творчі здібностіі | моя одноклассница. Чому це захищає від м'яса
Правда!Но только если ты уже знаешь, что ты строишь.
Коли я в першій фазі, мені не потрібна впевненість для реорганізації.
Hour 1: Write test for API I haven't designed yet
Hour 2: Realize the API is wrong, rewrite test
Hour 3: Discover a better approach, rewrite both
Hour 4: Finally get it working
Hour 5: Rewrite tests to match what actually works
Мне нужно разрешение, чтобы всё выбросить.
Hour 1: Explore 3 different APIs, settle on the best one
Hour 2: Refine the implementation, handle edge cases
Hour 3: Write comprehensive tests for what I built
Hour 4: All tests pass, commit with confidence
Аналізи створюють психологічний борг.
Навіть якщо вони випробовують неправильну річ.Аналізи до 3-ї фази я зберігаю фазу 1 і 2
психологічно дешевий.
Я можу:
Відкрий мою проблемунасправдірозв' язання
Ось як працює креативність.
// Test 1: Write test first (but I don't know the right API yet)
[Test]
public void TestProcessData()
{
var processor = new DataProcessor();
var result = processor.Process(new[] { 1, 2, 3 });
Assert.That(result, Is.EqualTo(new[] { 2, 4, 6 }));
}
// Implementation 1: Make it pass
public class DataProcessor
{
public int[] Process(int[] data) => data.Select(x => x * 2).ToArray();
}
// Later: Realize I need configurability, rewrite test
[Test]
public void TestProcessDataWithFactor()
{
var processor = new DataProcessor();
var result = processor.Process(new[] { 1, 2, 3 }, factor: 3);
Assert.That(result, Is.EqualTo(new[] { 3, 6, 9 }));
}
// Later: Realize I should use IEnumerable, rewrite test again
[Test]
public void TestProcessDataEnumerable()
{
var processor = new DataProcessor();
var data = GetLargeDataset(); // This should be lazy
var result = processor.Process(data, factor: 3);
Assert.That(result.Take(3), Is.EqualTo(new[] { 3, 6, 9 }));
}
Ты нарисовал первым.
Ви не починаєте з деталей.
// Phase 1: Explore (no tests yet)
public int[] ProcessThing(int[] data)
{
return data.Select(x => x * 2).ToArray();
}
// Try it: var result = ProcessThing(new[] { 1, 2, 3 });
// Hmm, what about configurability? What about lazy evaluation?
// Phase 2: Refine (still no tests)
public static class DataProcessorExtensions
{
public static IEnumerable<int> Scale(
this IEnumerable<int> source,
int factor = 2)
{
return source.Select(x => x * factor);
}
}
// Try it: var result = data.Scale(factor: 3).ToList();
// Much better API!
// Phase 3: Lock it down (NOW write tests, correctly)
[TestFixture]
public class DataProcessorTests
{
[Test]
public void Scale_DefaultFactor_DoublesValues()
{
var result = new[] { 1, 2, 3 }.Scale();
Assert.That(result, Is.EqualTo(new[] { 2, 4, 6 }));
}
[Test]
public void Scale_CustomFactor_ScalesCorrectly()
{
var result = new[] { 1, 2, 3 }.Scale(factor: 3);
Assert.That(result, Is.EqualTo(new[] { 3, 6, 9 }));
}
[Test]
public void Scale_LazyEvaluation_DoesNotEnumerateImmediately()
{
var enumerated = false;
var data = GetTestData(() => enumerated = true);
var scaled = data.Scale(factor: 2);
Assert.That(enumerated, Is.False, "Should not enumerate yet");
scaled.ToList();
Assert.That(enumerated, Is.True, "Should enumerate on materialization");
}
[Test]
public void Scale_EmptySequence_ReturnsEmpty()
{
var result = Enumerable.Empty<int>().Scale();
Assert.That(result, Is.Empty);
}
}
Як це підходить до агресивних і XP (І де вона розходиться)
це не "протест пізніший розвиток."
Це
щоб додати одиникові тести є критичними.
Вправи XP, які я зберіг
Кожна можливість проходить через CI перед об' єднанням
Співпрацездатне вдосконалення покращує дизайнПростий дизайн
Фаза 2 є
Реорганізація
Фаза 2 є відведеною часом реорганізаціїОчистити коддо
**Тестування захищає реорганізацію (у фазі 3+)**У чому я відрізняюсь від стриманого TDD
ТДД каже:"Спочатку тести.
Завжди.Ред-Зелёный-Рефактор - единственный способ."
Я кажу:"Тести, когда ты знаешь, что ты проверяешь.
Екскурс-Рефа-Лок є більш природним."
ДзЗУПІТЬ ТРИ-СЕШАтяте визначає інтерфейс }Видлат визначає інтерфейс ♪
♪ Test first for everything ♪ Test at the right time for every thing ♪
Саме час (Фаза):
Той же якісний результат.*Половина відтіку.*Це АзільAdvanced URLs: description or category
"Поміркуй, як зміниться план."ТДД може стати власною формою плану.
Як тільки ви записуєте тести, ви психологічно зобов'язані цьому дизайну.
Цебільше
Контрастливі підходи: приклад C#
Помните отрыв?
Три тестові переписи у процесі розвитку дизайну.
Запам' ятайте значення:
Фаза 1 потрапляє до програмного забезпечення для роботи
швидкий
Кооперувати, якщо допомагає (Фраза 2-3), досліджувати наодинці, якщо це не (Фраза 1)
Фаза 2 вирівнює параметри
після
Тому що:
Ти ще не знав, що таке крайня справа.Ви відкрили кращий дизайн APIВи зрозуміли, що функція взагалі не потрібна
Кожна спроба, яку ви пишете у фазі 1, ймовірно, марнується.
перший
набір тестів у фазі 3, коли ви насправді знаєте, що ви будуєте, ніж переписування під час дослідження.Обіцянка TDD проти реальностіОбіцянка ТДД:
"Write code that solves the specification.
Don't worry about perfection yet.
Just get it working."
"Напруження впливають на твій дизайн!
Тепер я переписую і тести, і код."**Це не дизайн.**Це
Бурчання.Мій підхід:
Розробка через реалізацію, а потім блокування тестами.
Без тестів*"Стривай і молися"*Коли я закінчу з 3-ю фазою мій код має:
Пробне покриттяОбробка випадків ребра
Очистити дизайн APIДокументація (через тести)
Точно то же, что и ТДД.
Різниця в тому, що
Правило просте:
- flake8 (style checking)
- pylint (code quality)
- mypy (type checking)
- black (formatting)
- radon (complexity analysis)
**Жоден код не залишає фази 2 без фази 3.**Я не:
for iteration in range(3):
# Measure current performance
metrics = measure_performance(code)
# Generate improved version
better_code = optimise(code, metrics)
# Keep if better, discard if worse
if better_code.score > code.score:
code = better_code
Передати неперевірений кодВідкрити PR без перевіркиОб' єднати у головне без передачі CI
Це не нова ідея.Ось як творча робота завжди працювала:
Маляри не починаються з фінальних мазків пензля.*Вони:*Sketchкомпозиція (Фаза 1)
Малювати*шари (Фаза 2)*ВарнішCity in Germany
# Test generation happens AFTER optimisation
def generate_unit_tests(specification, optimized_code):
"""Generate comprehensive tests for refined code.
This happens in Phase 3, after we know:
- What the code actually does
- What edge cases exist
- What the API surface looks like
"""
return llm.generate(
f"""Generate comprehensive unit tests for this code.
Specification: {specification}
Implementation: {optimized_code}
Include tests for:
- Happy path (from spec examples)
- Edge cases (discovered during optimisation)
- Error conditions (based on actual error handling)
- Performance bounds (based on measured metrics)
"""
)
для захисту і присутності (Фаза 3)
Вони:Чернеткаmoodly (Fase 1)
**для міцності (Pase 3)**Плавлення дуже важливе.
Як це зробити з коденими лініями DiSE*Ось тут стає цікаво:*реалізовано
ця філософія в системі ДіСЕ (прямій синтетичній еволюції).
Генератор
Не існує передчасної оптимізації
Спробувати знов з вищою креативністю (підвищення температури)
User: "Calculate fibonacci numbers"
[Phase 1: Exploration - 3 attempts]
Attempt 1: Works for small inputs, explodes on large ones (no safety limit)
Attempt 2: Adds safety limit, but inefficient recursive approach
Attempt 3: Switches to iterative DP (PASS)
[Phase 2: Optimization - 3 iterations]
Iteration 1: Add type hints, improve naming (Score: 1.05)
Iteration 2: Optimize memory usage with generator (Score: 1.10)
Iteration 3: Add input validation (Score: 1.15)
[Phase 3: Testing and Storage]
Generated 8 unit tests covering:
- Basic cases (n=0, n=1, n=5, n=10)
- Edge cases (n=negative, n=100, n=None)
- Type validation
All tests pass ✓
Stored in RAG with quality score: 1.15
Available for reuse: YES
Переміститися на більш потужну модель
точноФаза 1.
Ніяких тестів не написано протягом цього етапу.Код приватний до процесу створення.
Фаза 2 у DiSE: стадія ОптимізаціїПісля того, як код перейде до базової виконання, DiSE переходитиме доОптимізація.
(типово ітерацій)
Цеточно
Код все ще є приватним (не у списку), але ми його поліруємо:Кращі назви
Підказки типуЧиста структура
Нижча складність
Краща швидкодіяЦя форма вже не є рідиною.
Ми знаємо, що ми будуємо.
формальні тести одиниць.
Обкладинка тестів:*Приклади специфікації (правильність)*Виявлені випадки ребра під час фази 2
Параметри швидкодії, які було вимірено*Тоді,*лише якщо пропущено тести
Код такий:
Пам' ять RAG
# I know what sort() should do. Tests first? Sure!
def test_sort_empty_list():
assert sort([]) == []
def test_sort_single_element():
assert sort([5]) == [5]
def test_sort_multiple_elements():
assert sort([3, 1, 2]) == [1, 2, 3]
Доданий доРеєстр вузла:
у майбутніх роботах
оцінки якості
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.