The Kluever Maneuver
The Kluever Maneuver
The Kluever Maneuver Written by –Jordi October 31, 2026 In brief, it’s this: whenever you are doing a bugfix, do it in two commits. Write a passing test that exemplifies the buggy behaviour. Fix the bug and also fix the test we just wrote. For example, def add(a, b): return a + b + 1 and through your keen eye you observe that this addition function is buggy. You therefore write a test that records the bad behaviour, Commit 1 diff —git a/test_arith.py b/test_arith.py --- a/test_arith.py +++ b/test_arith.py @@ -0,0 +1,2 @@ +def test_add(a, b): + assert add(2, 2) == 5
Kluever 策略(The Kluever Maneuver)作者:Jordi,2026 年 10 月 31 日。简而言之,就是:当你进行 Bug 修复时,请分两次提交。首先编写一个能够体现错误行为的测试用例。然后修复 Bug,并同时修正刚才编写的测试。例如,对于 def add(a, b): return a + b + 1,你敏锐地发现这个加法函数有 Bug。于是你编写了一个记录错误行为的测试,提交 1:diff --git a/test_arith.py b/test_arith.py --- a/test_arith.py +++ b/test_arith.py @@ -0,0 +1,2 @@ +def test_add(a, b): + assert add(2, 2) == 5
Then comes the bugfix, Commit 2 diff —git a/arith.py b/arith.py --- a/arith.py +++ b/arith.py @@ -1,2 +1,2 @@ def add(a, b): - return a + b + 1 + return a + b diff —git a/test_arith.py b/test_arith.py --- a/test_arith.py +++ b/test_arith.py @@ -1,2 +1,2 @@ def test_add(a, b): - assert add(2, 2) == 5 + assert add(2, 2) == 4 BUT Y THO Two reasons: Demonstrable proof that the bugfix actually fixed the bug CI still passes at every commit The traditional way to do this is to do a bugfix and add a test in the same commit. But what if the test accidentally already passed before the alleged bugfix and the alleged bugfix didn’t actually work? In that case the added test gives false confidence that the bugfix worked.
接着是 Bug 修复,提交 2:diff --git a/arith.py b/arith.py --- a/arith.py +++ b/arith.py @@ -1,2 +1,2 @@ def add(a, b): - return a + b + 1 + return a + b diff --git a/test_arith.py b/test_arith.py --- a/test_arith.py +++ b/test_arith.py @@ -1,2 +1,2 @@ def test_add(a, b): - assert add(2, 2) == 5 + assert add(2, 2) == 4。为什么要这样做?有两个原因:能够证明 Bug 修复确实解决了问题,并且确保 CI 在每次提交时都能通过。传统做法是在同一次提交中修复 Bug 并添加测试。但如果测试在所谓的修复之前就已经意外通过,而修复本身并未生效呢?在这种情况下,添加的测试会给人一种修复有效的虚假信心。
Alright, show me Here’s a less obvious example. Suppose we want to make sure users are not underage, def age_in_years(birthday, today): return today.year - birthday.year def can_access(birthday, today): return 18 <= age_in_years( birthday, today ) You notice that this age check is buggy. It’s only considering the years. If it’s October 2026, but you were born on December 2008, you are not 18 yet. To fix it, the problem is just an off-by-one. diff —git a/dates.py b/dates.py --- a/dates.py +++ b/dates.py @@ -4,7 +4,7 @@ def age_in_years(birthday, today): return today.year - birthday.year def can_access(birthday, today): - return 18 <= age_in_years( + return 18 < age_in_years( birthday, today ) diff —git a/test_date.py b/test_date.py --- a/test_date.py +++ b/test_date.py @@ -0,0 +1,5 @@ +def test_can_access(): + assert can_access( + date(2007, 1, 1), + date(2026, 1, 1) + )
好的,演示一下。这是一个不太明显的例子。假设我们要确保用户未成年,def age_in_years(birthday, today): return today.year - birthday.year,def can_access(birthday, today): return 18 <= age_in_years( birthday, today )。你注意到这个年龄检查有 Bug。它只考虑了年份。如果现在是 2026 年 10 月,而你出生于 2008 年 12 月,你还没满 18 岁。要修复它,问题仅仅是一个“差一错误”(off-by-one)。(此处省略代码 diff)
The astute reader will observe the problem isn’t in the can_access function but in the age calculation. The bugfix isn’t an actual bugfix and the proposed test actually passes both before and after the proposed fix. The Kluever Maneuver would have prevented this. We wrongly check that someone born on New Year’s Eve in 2008 is eighteen on New Year’s Day in 2026. commit 1 diff —git a/test_date.py b/test_date.py --- a/test_date.py +++ b/test_date.py @@ -0,0 +1,5 @@ +def test_can_access(): + assert can_access( + date(2008, 12, 31), + date(2026, 1, 1) + )
敏锐的读者会发现,问题不在 can_access 函数中,而是在年龄计算中。这个 Bug 修复并不是真正的修复,所提议的测试在修复前后实际上都能通过。Kluever 策略本可以防止这种情况。我们错误地检查了一个 2008 年除夕出生的人在 2026 年元旦是否满 18 岁。提交 1:(此处省略代码 diff)
Then we fix the age calculation and flip the test. commit 2 diff —git a/dates.py b/dates.py --- a/dates.py +++ b/dates.py @@ -1,7 +1,10 @@ +from dateutil.relativedelta import relativedelta def age_in_years(birthday, today): - return today.year - birthday.year + return relativedelta( + today, + birthday + ).years def can_access(birthday, today): return 18 <= age_in_years( diff —git a/test_date.py b/test_date.py --- a/test_date.py +++ b/test_date.py @@ -1,5 +1,5 @@ def test_can_access(): - assert test_can_access( + assert not test_can_access( date(2008, 12, 31), date(2026, 1, 1) )
然后我们修复年龄计算并翻转测试。提交 2:(此处省略代码 diff)
What about bug tracking? Ah, yes, it’s also a good idea to log a bug in your tracker. Reference the bug in your first commit. add a test showing the bug diff —git a/test_arith.py b/test_arith.py --- a/test_arith.py +++ b/test_arith.py @@ -0,0 +1,2 @@ +FIXME: bug #1729 +def test_add(a, b): + assert add(2, 2) == 5 Record the fix in the second commit: remove off-by-one error fixes: #1729 diff —git a/arith.py b/arith.py --- a/arith.py +++ b/arith.py @@ -1,2 +1,2 @@ def add(a, b): - return a + b + 1 + return a + b diff —git a/test_arith.py b/test_arith.py --- a/test_arith.py +++ b/test_arith.py @@ -1,2 +1,2 @@ -FIXME: bug #1729 def test_add(a, b): - assert add(2, 2) == 5 + assert add(2, 2) == 4
那么 Bug 追踪呢?啊,是的,在追踪系统中记录 Bug 也是个好主意。在第一次提交中引用该 Bug。添加一个显示 Bug 的测试:(此处省略代码 diff)。在第二次提交中记录修复:移除差一错误,修复:#1729:(此处省略代码 diff)
Okay but why “Kluever Maneuver”? Oh. Because my colleague Kurt Kluever taught it to me, and I immediately think back to him when I think of this technique. He usualy calls it “terrible tests”, as in “Jordi, can you please write a terrible test for this?” and off I go to write a test asserting something I know to be wrong.
好的,但为什么叫“Kluever 策略”?哦,因为我的同事 Kurt Kluever 教给我的,每当我想到这个技术时,我就会立刻想起他。他通常称之为“糟糕的测试”,比如:“Jordi,你能为这个写一个糟糕的测试吗?”然后我就去写一个断言我知道是错误的东西的测试。