Sorry for such a long answer, but I thought that my thought process would be more consistent if I survived all this.
Since this question is marked by TDD, I assume you are writing a TDD style method. If so, you can start with:
describe "my_square_function" do it "Squares a positive number" do my_square_function(1).should == 1 end end
After a failure test, you can implement my_square_function as follows:
def my_square_function(number) 1 end
Now that the test passes, you want to reorganize the duplication. In this case, duplication is performed between the code and the test, that is, the letter 1. Since the argument carries the value of the test, we can remove the duplication using the argument instead.
def my_square_function(number) number end
Now that the duplication has been removed and the tests still pass, we can move on to the next test:
describe "my_square_function" do it "Squares a positive number" do my_square_function(1).should == 1 end it "Squares a negative number" do my_square_function(-1).should == 1 end end
Running the tests is again welcomed by the failed test, so we will skip it:
def my_square_function(number) number.abs
Now this test passes, and it's time to move on to another test:
describe "my_square_function" do it "Squares a positive number" do my_square_function(1).should == 1 end it "Squares a negative number" do my_square_function(-1).should == 1 end it "Squares other positive numbers" do my_square_function(2).should == 4 end end
At this point, your newest test will no longer pass, so now to pass:
def my_square_function(number) number.abs * number end
Unfortunately. This didnβt quite work, it led to the fact that our test of the negative number failed. Fortunately, the failure pointed us to an exact test that did not work, we know that this did not succeed due to the "negative" test. Return to code:
def my_square_function(number) number.abs * number.abs end
So, all our tests pass now. It's time to reorganize again. Here we see some other unsolicited code in these abs calls. We can get rid of them:
def my_square_function(number) number * number end
The tests still pass, and we see more duplication with this annoying argument. Let's see if we can get rid of it:
def my_square_function(number) number ** 2 end
The test passes, and we no longer have this duplication. Now that we have a clean implementation, let's look at the following nil case:
describe "my_square_function" do it "Squares a positive number" do my_square_function(1).should == 1 end it "Squares a negative number" do my_square_function(-1).should == 1 end it "Squares other positive numbers" do my_square_function(2).should == 4 end it "Doesn't try to process 'nil' arguments" do my_square_function(nil).should == nil end end
Well, we will return to failure again, and we can continue with the nil check:
def my_square_function(number) number ** 2 unless number == nil end
This test passes and it is pretty clean, so we will leave it as is. Now we go back to the specification and see what we have and make sure that we like what we see:
describe "my_square_function" do it "Squares a positive number" do my_square_function(1).should == 1 end it "Squares a negative number" do my_square_function(-1).should == 1 end it "Squares other positive numbers" do my_square_function(2).should == 4 end it "Doesn't try to process 'nil' arguments" do my_square_function(nil).should == nil end end
My first tendency is that we really describe the behavior of squaring a number, not the function itself, so we change this:
describe "How to square a number" do it "Squares a positive number" do my_square_function(1).should == 1 end it "Squares a negative number" do my_square_function(-1).should == 1 end it "Squares other positive numbers" do my_square_function(2).should == 4 end it "Doesn't try to process 'nil' arguments" do my_square_function(nil).should == nil end end
Now, three example names are a little crazy when placed in this context. I'll start with the first example, it seems a little cheesy square 1. This is the choice I'm going to make to reduce the number of examples in the code. I really want the examples to be interesting in some way, or I will not test them. The difference between squares 1 and 2 is uninteresting, so I will remove the first example. At first it was useful, but no more. This leaves us with:
describe "How to square a number" do it "Squares a negative number" do my_square_function(-1).should == 1 end it "Squares other positive numbers" do my_square_function(2).should == 4 end it "Doesn't try to process 'nil' arguments" do my_square_function(nil).should == nil end end
The next thing I will consider is a negative example related to the context in the described block. I am going to give him and other examples new descriptions:
describe "How to square a number" do it "Squaring a number is simply the number multiplied by itself" do my_square_function(2).should == 4 end it "The square of a negative number is positive" do my_square_function(-1).should == 1 end it "It is not possible to square a 'nil' value" do my_square_function(nil).should == nil end end
Now that we have limited the number of test cases to the most interesting ones, we donβt really have much to do. As we saw above, it was nice to know which line crashed if it was another test case that we did not expect. By creating a list of scripts to run, we lose this feature, which makes debugging difficult. Now we could replace the examples with dynamically generated it blocks, as mentioned in another solution, however we are starting to lose the behavior that we are trying to describe.
So, in general, limiting your test scenarios to only those that describe interesting features of the system, your need for too many scripts will be reduced. In a more complex system, with many scenarios, it is probably emphasized that the object model probably needs a different image.
Hope this helps!
Brandon