During the introduction, we answered nearly each and every one of these questions — who, what, where, when and why.
The only question that remains is how — how do we test from the inside?
For the remainder of the course I'm going to walk you through different techniques and approaches that you can do to develop your skill of testing from the inside.
Let's get started with our first example.
Let's say I give you an application around social security, and I show you the interface, and I said, "Hey, you know what? This application has a field around social security numbers, and I want you to test that aspect of the application."
I'm sure you're all thinking already, "Well, okay. With my knowledge of this particular problem, let me try to enter a valid social security number, and make sure that a user can progress through the application.
Or, what if I leave it blank? Is it required, and should it give me an error message and guide the user towards the correct input? What about alphabetic characters? If it's only supposed to take numbers, they should also get a rejection, and some sort of guidance, around that particular test."
We can continue to throw and hammer this application with inputs, but let's not forget that there's an implementation behind this application. What if I told you that you could see the code? Does that make a difference? And I said, "You know what? This is the code."
You can pause and look at this for a while, but for some you, this might be the first time that you are looking at a program implementation like this, and you're probably telling yourself, "Well, this just looks like a foreign language."
And you're right. This is exactly just that. It is a foreign language if this is the first time that you're seeing it. But just like others have learned this language, and learned the syntax, and the semantics around how this language works, you can too.
We're actually going to start that today, by looking at this particular example, and how to test this field using knowledge of this implementation. And at the same time, just understanding how this language is structured.
The first thing to note is that this function is defined for processing the social security number. It starts here with the keyword
public, and then the braces that you see enclose the instructions that the computer uses, and the compiler uses, to actually carry out this particular function.
Let's walk through this line by line.
public void Foo(Window SSN)
The first thing you'll see is this keyword
This really defines the scope of this function. What does that mean? It's defines about who can access this function. Just like the word “public” suggests, it's saying that this function can be accessed by anyone.
Similarly, you can think, "Well if something is public, then there might be a keyword that defines when it's private." If it were
private, it means that whatever is the parent program of this is the only program that can access it. Usually this is embedded in a single class, and those private methods are only accessible by that class.
However, if it's public, it means that other classes can actually access this function and this code.
Next, you'll see what is the “return type” of the function —
We talked in the introduction that a program is really just a function of inputs to outputs. You have x and you produce ƒ(x). And so ƒ(x) can have a type.
In other words, if we were calculating the average of a particular set of numbers, maybe the type is decimal and we may expect a real number from that average. So, here you can define the return types in terms of real numbers or integers, or in this case, this function actually returns nothing.
It's not really a function in the purest form since it doesn't return a value, but it's more of a procedure. It's just a set of instructions that get executed, and there's no need for a value to come back.
The next thing that we see here is
Foo just represents the name of the particular function. In this case, Foo also takes in some parameters —
The parameter list, that's your
x that feeds into the function as a seeded value and allows the program to use that value as part of its calculation. So, if my function was the square root, and my
x was 16, then I know that I would apply my square root function to that parameter, and my answer would be 4.
Here, what we declare in our parameter list is first the type of the parameter, in this case we're seeing that this parameter that we're using, or that we're sending in, is really a “window,” and so some aspect of the user interface. And the name of it is “social security number”, or SSN.
Let's just go through that one last time.
public, accessible by everyone.
void— we don't return anything from this function.
Window, or UI, that we looked at in the previous slide.
Now that we understand what is really the “signature” of this function, we can get into the actual code block that is defined.
if (SSN.Text == “246779876”)
The initial thing is that we have this statement —
It says, "if the
SSN.Text is equal to this specific number, then I want to get these instructions processed by the compiler."
And so, the system will execute these lines only if this statement is true —
else, it will jump into this block and execute this set of statements instead.
else //Actual code to process the SSN
Here, just for the purposes of simplicity, we've not put all of the code for the actual processing of the social security number. We've just put a comment —
// Actual code to process the SSN — which we have these two forward slashes, followed by any text that we want to just represent a note, or some comment, that we can leave in the code. So, here we're just ignoring that aspect for now and focusing on this portion — the
Would we have thought during our testing from the outside to put in this specific social security number? I doubt it.
We might have come up with it, but it's highly unlikely that we would have seen this specific value as something that we should enter if we only had the information from the outside. And in which case, we would never have executed this code. We would always have jumped into this
It becomes important to look at the code because, unfortunately, even though this is not a very good programming practice, sometimes when we're evolving and maintaining code, how we realize that there's some special case, programmers will come in and handle a special case inline using a block like this.
Let's get an understanding at a high level of what's happening here.
We have already seen that if we have this value (“246779876”), we know we're going to execute these statements.
int arr = new int;
The first thing is that we have a declaration of a space in memory to hold a set of integers.
intrepresents the type integer, which is just a positive or a negative whole number.
Here we're seeing that we have an integer array, that we're going to call “arr”, how we want it to be a new array of integers of size 32. That's saying that whenever I refer to the variable
arr, that I have some space for a list of positive or negative numbers that is a list that can hold 32 of those elements.
Now let's look at what happens in terms of processing this particular list for this particular section of code.
Here we see a loop, right? Some sort of iteration over that list.
for (int i = 0; i < 100; i++) arr[i] = i;
And it's saying that for some integer,
i, I want to be able to increment — this is where you see the
++ is the increment — from
i=0, all the way up to
i is around 99, right? It says "i is less than 100".
So, this loop is going to go from 0 to all the way up to 99 in increments of 1. It means that we're going to iterate for 100 times, from 0 to 99. In computer science we love to count from 0.
What we're going to do is we're going to access that list in the position that i refers to. The first time i will refer to 0, in that 0th position, we're going to actually going to put the number 0.
When this becomes 1, because we loop again, we're going to put in the second position, in array 1, the number 1. From looking at this code, the idea is that we're going to populate this array with the numbers 0 all the way up to 99.
But wait, there's a problem. A very obvious problem. The obvious problem is that we didn't actually allocate 100 spaces, we only allocated 32. And so, when this array of reference to this particular index goes past 32, the system is going to crash.
Let’s take a step back. Now that we understand that at high level, when we enter this specific social security number, the system actually breaks and fails.
Now we understand the importance of being able to look at this information and leverage it to do more thorough testing.
Because our black-box approach, or our testing from the “outside of” approach, probably wouldn't have yielded this value as part of our testing. But by looking here, and just inspecting this code, we actually could have seen that there's an issue around this specific social security number value.
Of course, this example is a little bit of an exaggeration, but it is representative of real things that happen in production code.
This brings us to this first section of testing from the inside, which I like to call testing by looking.
Testing by looking really involves combining reviews, inspections, or walkthroughs to either look for defects and come up with test cases that allow you to test the program more thoroughly and effectively.
The review is just a general term for any type of inspection, which is a more formal process, or a walkthrough, which is an informal process, that allows you to find defects. Things like code reviews, and pair programming.
The inspection, like we said which is more formal, involves coming up with a predefined checklist of things that you're looking for, and actually examining the artifacts.
The walkthrough is more of an informal meeting, and you have an engineer actually step through the artifact using test data.
The quiz for Chapter 2 can be found at the end of Section 2.3