In part two I’ll keep testing the prime? method and then I’ll make a new method that uses the same algorithm and that way show some refactoring.
Let’s start by checking for faulty input. A prime number can’t be negative so there’s no use checking for primes in values below zero. It might seem harsh but I decided to throw an exception when the user tries to input a negative number into my prime method. For this I’ll use the ArgumentError that comes with standard Ruby. I could create my own NegativeArgumentError (which is quite easy) if I wanted but that’s beside the focus of this post.
So, as usual I start with writing the test first.
def test_negative_input_to_prime_raises_exception assert_raises(ArgumentError) {@primality_tester.prime?(-2)} end
Here I tell the framework that I am expecting the function to raise an exception by the name ArgumentError when I send in a negative value.
Running this I get a failed test. ArgumentError is not thrown, instead a DomainError is thrown from the sqrt-function (if you know your math you know that you can’t do the square root of negative values unless you use complex numbers). We’ve reached the red phase, test fails. Now, let’s make it pass.
Ok, simplest solution first.
def prime?(num) raise ArgumentError if num == -2 # More code here end end
Run tests and they pass.
Ok, next test, another negative number. Let’s say -23.
Tests fail.
And now I’ll make sure to plug that hole.
def prime?(num) raise ArgumentError if num > 0 # More code here end end
Tests pass.
Do I need to test more negative numbers? No, not really. I know that I got the > right as it takes negative numbers and not positive and there’s no real edge cases to test (not entirely true, zero would be an edge case but I’ll test zero later) so I’ll be happy and done with negative numbers.
And finally there’s a few edge cases aswell that I want to test. These being 1 and 0. Per definition 1 is not prime eventhough it can only be divided by 1 and itself. Let’s make sure 1 is not considered prime.
Test first and make sure it fails.
def test_1_is_not_detected_as_prime refute( @primality_tester.prime?(1), "1 should not be considered prime") end
Let’s make it pass now.
def prime?(num) raise ArgumentError if num < 0 return true if num == 2 return false if num % 2 == 0 or num == 1 # More code after this end
For the observant of you, you’ll notice that I did a small refactoring here.
Before I returned true if num % 2 was not zero. Now I changed that into returning false if num % 2 is zero, I also tacked on 1 at the end. All my tests pass so the refactor did not break anything.
And the last test on the prime? method. 0. 0 is not prime and should thus return false.
When I run the test for 0 I get pass from the start, no failed test there. Why? Well, let’s look at the code again.
def prime?(num) raise ArgumentError if num < 0 return true if num == 2 return false if num % 2 == 0 or num == 1 # More code here end
0 divided by 2 is 0. So our case of 0 is caught by line 4 already. Alright, since I could verify that so easily I’ll not make it fail before I again make it pass.
Am I done?
Not quite. Now I know that the function detects primes fine, it raises an exception for negative input and the edge cases 0 and 1 are not considered prime. But, what about non primes, does it really return false for non primes? For all even it does but what about 9, 15, 21, 51? I’ll make a test covering that aswell. To make sure even are not detected prime I’ll also put 4 in the list.
[4, 9, 15, 21, 51].each do |p| define_method :"test_that_#{p}_is_not_detected_as_prime" do refute( @primality_tester.prime?(p), "Expected false, got true") end end
And those tests pass aswell.
Are there any more tests that could be done here? There might be a few but I’m quite happy with the ones that are already in there. Now I know that the function raises exceptions for negative input, that it gives me correct answers for values prime and not prime, and I’ve tested the edge cases.
Leave a comment