I tried it on Bob Martin's "Prime Factors" Kata solution, translated into Python:<p><pre><code> def prime_factors(n: int):
primes = []
candidate = 2
while n > 1:
while n % candidate == 0:
primes.append(candidate)
n //= candidate
candidate += 1
return primes
</code></pre>
It gave a decent initial unit test:<p><pre><code> import unittest
class TestPrimeFactors(unittest.TestCase):
def test_prime_factors(self):
self.assertEqual(prime_factors(4), [2, 2])
self.assertEqual(prime_factors(9), [3, 3])
self.assertEqual(prime_factors(12), [2, 2, 3])
self.assertEqual(prime_factors(17), [17])
self.assertEqual(prime_factors(24), [2, 2, 2, 3])
if __name__ == '__main__':
unittest.main()
</code></pre>
Well done! It generate cases with duplicates factors, which is the biggest likely mistake in this algorithm. (It's also possible to use "/" instead of "//", but that doesn't cause a failure until working with values above 2^53 or so, like 3^53.)<p>It's not done in Martin's one-test-per-test-case style, but that's fine - I prefer this more compact form.<p>I chose this Kata because it's very simple, and lets us examine what the generated test suite doesn't cover.<p>Like, there's no test for 1, 0, or negative number, which will return an empty list.<p>We can use hypothesis to help identify that case:<p><pre><code> import math
from hypothesis import example, given, strategies as st
@given(st.integers())
def test_product(n):
factors = prime_factors(n)
assert math.prod(factors) == n
if __name__ == '__main__':
test_product()
</code></pre>
reporting<p><pre><code> assert math.prod(factors) == n
AssertionError
Falsifying example: test_product(
n=0,
)
</code></pre>
With st.integers(min_value=2) we then find a performance issue. It tries to factor 5123983848966349269, but Martin's algorithm is extremely slow at computing [3, 7, 1049, 90631, 2566470031] because it must count to 2566470031, in Python, by ones.<p>The upper-bound was never specified in Martin's formulation of the Kata. It was written in Java using integers, so the Mersenne prime 2^31-1 is allowed, but even that will take a long time to compute.<p>Using a max_value of 1,000,000 has Hypothesis finish with no other reported problems.<p>I think all that's needed is an extra bounds check:<p><pre><code> if not (2 <= n <= 1_000_000):
raise ValueError("out of range")
</code></pre>
and corresponding test:<p><pre><code> def test_bounds(self):
for n in (-1, 0, 1, 1_000_001):
with self.assertRaisesRegex(ValueError, "out of range"):
prime_factors(n)
</code></pre>
Mutation testing (with mutmut) shows no further issues.<p>Again, well done on coming up with a good set of initial cases, automatically.