from Hacker News

Paste a block of code here and we will generate a series of unit tests

by oliviergg on 1/17/23, 8:58 PM with 2 comments

  • by rjvs on 1/27/23, 6:58 PM

    What privacy policies and governance applies to code pasted into your form?
  • by eesmith on 1/17/23, 10:17 PM

    I tried it on Bob Martin's "Prime Factors" Kata solution, translated into Python:

      def prime_factors(n: int):
        primes = []
        candidate = 2
        while n > 1:
          while n % candidate == 0:
            primes.append(candidate)
            n //= candidate
          candidate += 1
        return primes
    
    It gave a decent initial unit test:

      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()
    
    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.)

    It's not done in Martin's one-test-per-test-case style, but that's fine - I prefer this more compact form.

    I chose this Kata because it's very simple, and lets us examine what the generated test suite doesn't cover.

    Like, there's no test for 1, 0, or negative number, which will return an empty list.

    We can use hypothesis to help identify that case:

      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()
    
    reporting

          assert math.prod(factors) == n
      AssertionError
      Falsifying example: test_product(
          n=0,
      )
    
    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.

    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.

    Using a max_value of 1,000,000 has Hypothesis finish with no other reported problems.

    I think all that's needed is an extra bounds check:

      if not (2 <= n <= 1_000_000):
        raise ValueError("out of range")
    
    and corresponding test:

      def test_bounds(self):
        for n in (-1, 0, 1, 1_000_001):
          with self.assertRaisesRegex(ValueError, "out of range"):
              prime_factors(n)
    
    Mutation testing (with mutmut) shows no further issues.

    Again, well done on coming up with a good set of initial cases, automatically.