Welcome back. We've already seen how to implement a test for a function.
You just invoke test.testEqual where their first input is
an invocation of the function and the second input is the correct value,
the correct output for the function.
In this video, we're going to consider what tests are good to implement.
It's useful to distinguish between two kinds of things that a function might accomplish.
One is that it might return a value,
and the other thing is that it might have a side effect.
By a side effect,
I mean that it might change the contents of a list or change the contents of
the dictionary or might also write to a file or produce some output in the output window,
but we're not going to use test.testEqual for
checking on writing to a file or the output window,
but we will use test.testEqual to see if
a list has been modified or a dictionary has been modified.
So, I'll call the two different kinds of tests,
a return value test or a side effect test
depending on what it is that the function was supposed to be doing.
Let's first consider return value test.
So, this is a function like square,
where its whole job is just to compute a new value.
It gets an input, it produces an output,
it's not changing the contents of any list or dictionary.
So, which inputs should we give it as tests?
One way to think about that is to think about the equivalence classes of inputs,
because we're not going to be able to test every single possible input.
So, maybe we should for something that's computing squares or some arithmetic thing,
maybe one equivalence class of inputs are positive numbers.
You already see we've got one test where we're passing in three as an input.
But perhaps, we ought to have another input which would be negative number.
Maybe if we somehow implemented this wrong,
it would work for the positive numbers,
but not work for the negative numbers.
So, it would be a good idea to have a test of some negative value.
That if we square minus four we should get positive 16, not negative 16.
It's also a good idea to think of,
what other classes of inputs could we get?
Maybe we could get a floating point number.
Maybe it only works on integers for some reason.
So, suppose I had two point,
well, I need to do something where I can actually compute it myself.
Let's see, 1.5, what's the square of 1.5?
It should be 2.25, I believe.
Let's check that to be sure.
Yes, I passed all three of those tests.
You'll notice this is an important point.
When you're writing a test you got to figure out which inputs should you run a test on,
and you have to know what is the correct output.
In that case, I was a little unsure about the square of 1.5,
but it is 2.25.
Then you also should think about sort of
the boundaries between these equivalence classes.
So, I did a positive number,
I did a negative number.
Gee, what happens if we do zero? We'll get that right.
So, it's a good idea to have a test for these boundary conditions,
these edge or extreme cases.
It's also helpful to think about what might go wrong.
Like, what could I possibly do wrong and this is a very simple function,
it's hard to think of things that could go wrong,
but maybe I did it with a plus instead of times.
If I were to do test.testEqual square of two,
I would still get this right.
So, I've failed most of my test,
but square of two came out to be four even though I did it wrong.
So, two isn't really a great choice, or if I do two,
I should also do something like three,
that would distinguish between a correct implementation and an incorrect implementation.
It's also worth thinking about
return value test for functions that have optional parameters.
If a function takes an optional parameter,
one of the edge cases to test for is
when no parameter value is supplied during execution.
So, let's consider the built-in sorted function.
You remember that it has an optional parameter reversed.
So, I can call sorted of one,
seven and four and not specify any value for the reverse parameter, it's optional.
If I don't specify the reverse parameter at all,
I should get the results not reversed.
I should get one, four and seven.
If I call sorted of one, seven,
four with reverse equals true,
then I should get the values in the opposite order,
seven, four and one.
So, I should pass both of these tests.
Just to be complete,
I might want to have one more test here, which is,
what happens if I call sorted on one,
seven and four, but I say reverse equals false.
So, I've actually provided this parameter.
I should get the same value that I would get if I left out that
parameter entirely and just got the default value because the default value is false.
So, this one should pass as well.
Of course, if I changed any of these,
seven, one and four,
now I'm going to fail the second test.
It said, we expected to get seven, one, four,
this is what we specified was supposed to be the value,
but we actually got seven, four, one.
Now, in this case, we failed the test because I wrote a bad test.
The correct answer when we pass one, seven,
four to sorted with reverse equals true,
the correct answer is seven, four, one.
Now, we've passed it. In summary,
you'll use a return value test when the purpose of
a function is to compute an output from its inputs.
You express the test by calling test.testEqual.
The first parameter's an invocation of the function,
the second is the correct output,
which you had to compute manually.
You should make one test case for each of the equivalence classes of
inputs and don't forget the edge or extreme cases.
When you're dealing with a function that has optional parameters,
some of those edge or extreme cases should
include: omitting or including the optional parameters.
Now, the order of creating test cases is that you're going to create several of them.
When you've correctly implemented the function that you're testing,
you will pass all of the tests,
but you want to have what's called good coverage of those tests.
They need to cover all the ways in which you
might make a mistake in implementing the function.
So, if there was some error in the implementation of the function,
then one of your tests ought to fail.
But if the function is correctly implemented,
it should pass all of the test.
The order of this is to pick a good set of tests,
so that you'll fail if the function is not implemented correctly,
but you'll pass them all when the function is
implemented correctly. I'll see you next time.