Chapter 25: Tinkering, Testing, and Debugging Revisited

We’re friends. So, between us friends, we have a confession to make.

Our book Captain Code, it’s pretty cool, right? Yeah, we think so, too. But, and this is embarrassing to admit, there’s a problem with it. Yep, it’s not quite perfect. Why? We had so much we wanted to share with you that we ran out of space! Seriously! Oh, the horror!

So, what to do? Well, we came up with a solution. One more bonus chapter, right here online, for you to read, download, print, as you see fit. Here goes!

Tinkering Made Easy

As you’ve learned in our 24 chapters together, coding is all about creative problem solving, breaking down problems into smaller bite sized chunks, and smartly using the resources available to you to solve those problems.

And those resources? There are lots of them. The standard Python installation comes with 200 or so libraries and thousands of functions and methods. On top of that, there are over 100,000 3rd party libraries available, too, and they also have lots of functions and methods. So, yeah, lots and lots of resources. You’re not going to remember every single detail of every single method. Nor should you. After all, you have documentation and Google at your disposal!

And you are going to want to tinker. Lots. If you want to understand a library, a method, or some sample code you find online, you’re going to need to try them. One way to do this, what we’ve done throughout the book, is to create test programs which print results when executed.

But Python provides another super fun and easy way to test code.

We’d like to introduce you <drumroll please> to the Python interpreter. Here’s what you need to do:

  1. In Visual Studio Code open a terminal window (as a reminder, that’s the window which shows your code’s output). If needed, open the Terminal menu and click on New Terminal to open the window.
  2. At the comment prompt in the Terminal window, type the following:
    • Windows: python
    • Mac: python3
  3. You’ll see Python version information displayed (yours may not be the same as ours, if that’s the case, don’t worry), followed by a >>> prompt, like this:
  1. You can now type Python commands after the >>> prompt.
  2. When done, type exit() to exit the Python interpreter, returning to the standard Terminal window display. (Remember the exit() function? We used it to quit our Crazy Driver game).

Ok, so what can we do in the interpreter? Honestly, anything you can do in a single Python file. Let’s try some examples. Launch the Python interpreter (just as we did a moment ago), and then type the following:

print("Hello from the Python interpreter")

Then press Enter to execute the print() statement. You’ll see the results displayed right away, like this:

The Python interpreter is a great place to experiment with using different variables. For example, you could create a list:

fruit=["apple","pear","orange","kiwi"]

You can then work with the variable as needed:

print(fruit)
print(len(fruit)
print(fruit[2])

You can type any code you like, then press Enter, and … tada! … results.

You can also import libraries, right within the interpreter:

import random
print(random.randrange(1,101))

Oh, and here’s a fun tip. Within the interpreter you can use your keyboard’s arrow up and arrow down keys to display previous lines of code, find the one you want, edit it if needed, and press Enter to execute.

As you can see, the Python interpreter makes it oh so easy to tinker and test.

The Debugger

As much as we’d like all of our code to work perfectly, debugging is a fact of life for us coders. How did we debug our code? By sticking temporary print() statements right within code we were able to display variables, follow execution paths through loops and conditions, and more. Embedded print() statements are a simple yet useful way to debug code.

But there’s another way to debug your code. Using Visual Studio’s powerful interactive debugger, you can execute your code line by line and see exactly what’s going on under the hood. Actually, the truth is that it is Python itself that supports interactive debugging. Visual Studio uses this capability and provides it with a slick and powerful user interface.

Oh, and as an added benefit, just as Visual Studio supports all sorts of languages (as we discussed in Chapter 1), the Visual Studio Debugger supports debugging those languages. So, once you earn how to use the Debugger with Python, you’ll know how to use it with any language that supports interactive debugging.

Ok, so let’s give this a try. Here is a program that generates 10 unique random numbers. Copy the code and save it.

# Generate 10 unique random numbers

# Import
import random

# List to store results
numbers = []

# Loop until we have 10 numbers
while numbers.count(10) < 10:
    # Generate a random number
    num=random.randrange(1,101)
    # Do we have this one already?
    if not num in numbers:
        # No, so add it to the list
        numbers.append(num)

# Display the numbers
print(numbers)

You can try running it if you’d like. But, be warned, there’s something horribly wrong with it. The code won’t display any errors. Actually, it displays nothing at all, even though it seems to run forever. So, if you do try to run it, you’ll eventually need to quit the Terminal window to kill the running program. Ouch!

So, yep, the code is syntactically valid (meaning the code is all valid code), but it is definitely broken (or, as coders might say, it’s behaving as coded but not as intended).

We could use print() statements to try to figure out what is going on. But, instead, let’s use the Debugger. Here’s how it all works.

Accessing the Debugger

To use the Debugger, you’ll first want to switch to the Debugger screen. Look at the icons on the top left of Visual Studio, you used one of those previously to install Python libraries.

This time click on the Debugger icon, it looks like the image on the left, an Execute triangle but with a bug on it. You can click this icon any time to go to the Debugger screen. To get back to the usual Explorer view, just click the top icon.

So, click the Debugger icon. You should see a screen like this:

If your screen shows windows and options on the left side of the screen, don’t worry, it’ll still work, we’ll explain those in a moment. For now, we care more about the code window.

Setting Breakpoints

The magic of interactive debuggers is that they allow you to run your code line by line by line, pausing after each so you can look at code processing paths, variable values, and more. You just need to tell the Debugger where to pause so you can take control. And you do this using Breakpoints. A breakpoint is a mark you put next to a line of code (or next to multiple lines of code). The Debugger will have Python execute your program as it usually does. And then, when the breakpoint is reached, program execution will stop, and you can take control.

So, how do you set breakpoints? Easy. We just need to decide where we want to start debugging from. We know that there’s nothing wrong with the import random line. And the line of code that just creates numbers = [] is obviously fine, too. So, we’ll start interactive debugging from the line with the while loop.

Find the line of code with the while loop in it, in our example it is Line 10. Then move your mouse to the left of the line number. You’ll see a dark red circle displayed. You can mouse up and down until the dark red circle is on the line you need. When it is, click it, and the dark red circle will turn bright red. That’s it, you’ve created a breakpoint.

Here’s what the screen will look like, notice the red circle next to line 10 showing us where the breakpoint is. (Also notice that breakpoints are listed in a BREAKPOINTS window at the bottom left of the screen).

Breakpoint indicators are toggles. What that means is that you can click to place a breakpoint, then click again to remove it, and click again to place it, and so on.

Oh, it’s worth noting that you can have as many breakpoints as you want or need. You’ll generally want at least one when you start debugging, otherwise the application will execute and not stop anywhere. And we just need one so once you have set the breakpoint on the while line we can start debugging.

Starting the Debugger

There are a couple of ways the Debugger could start depending on how VS Code is configured. So, let’s look at both.

When you open the Debugger screen, you may see a prompt like this:

If this happens, click the blue Run and Debug button. You may then be prompted with a series of options like this:

If you get this prompt, select Python File (duh!), and the Debugger will start.

If VS Code didn’t display that big blue Run and Debug button, then you can just click the green Start Debugging button at the top left of the screen.

Either way, by this point you’ve started the Debugger. Ok, so now what do we do with it?

A Tour of the Debugger

Now you have the Debugger open, let’s take look at the screen and user interface which looks like this:

We know that it looks a little complicated, but there are actually just a few things you really need to pay attention to for now:

  • Your code is displayed right where you’d expect it, on the upper right of the screen. A colored bar (it looks kinda olive green) indicates the line of code that the Debugger stopped at, here it’s line 10 where we placed our breakpoint.
  • And right below the code is the Terminal window which will display output. You’ll see more Python commands displayed there when debugging, but for the most part you can ignore these.
  • The left side of the screen contains a series of windows. The first is named VARIABLES and as you step through your code it will display all variables and their values. It will always show any special variables (including the built-in ones like __file__ which we used in Part 3). It will also show our numbers variable (which was created before the Debugger stopped at the breakpoint), and as VS Code knows that numbers is a list which could contain multiple values it allows expand and collapse the variable to see what is inside of it. The variables shown in this window will change to always reflect the variables currently available and in use in your code.
  • Beneath the VARIABLES windows is the WATCH window which lets you type Python expressions to watch. These can be variables, but also functions, and as you step through your code, you’ll see changes reflected here. We’ll look at this window in a few moments.
  • Beneath that is the CALL STACK window, it shows you where you are within the program, what functions were called and in what order so you can understand how you got to a specific line of code. This truth is that this window isn’t useful when debugging simple programs like ours, but it is super useful when debugging bigger multi-file programs.
  • Above the editor window is this toolbar with six buttons. This is used to control debugging, and some buttons may be grayed out and unavailable depending on what exactly you are doing.
  • And finally, above the VARIABLES window is a green Execute button. It may look like the regular execute button (which is still available on the upper right of the screen) but this one is different in that it executes our code in the Debugger.

There are other options and goodies here, but we’ve covered all we really need for now.

Stepping Through Code

Your program is now running in the Debugger. Or, rather, it has paused in the debugger waiting for you. It’s time to use the Debugger controls. Click the 3rd button (the one with a blue arrow pointing down to a blue dot). This is the Step Into button, it will advance code processing one line, and you’ll see the colored bar showing where the Debugger is will move to the next line of code.

You can keep stepping through line by line. But let’s try clicking the 4th button (the one with the blue arrow pointing upwards). This is the Step Out button and it will step out of the current code. If you are in an if statement it’ll execute until if processing has completed. If in a user-defined function it’ll finish executing the function and pause when control is returned to the calling function. And if in a loop, like we are, it’ll execute until the for or while is evaluated.

Let’s look at one more, the first button (the one that looks like a blur triangle play button) is the Continue button. This one simple runs until the next breakpoint, so in our code each time you click that it’ll execute a while loop and then pause.

Inspecting Variables

When you are using the Debugger, you are primarily looking at two things:

  • Program flow, meaning how you loop and until when, what happens when if statements are evaluated, and so on.
  • Variable values and how they change as your code executes.

Stepping through code lets you see program flow. But how do you look at variables and their values?

We mentioned one option already, the VARIABLES window shows you variables as hey change. As you step through our program, you’ll see numbers growing by one value each time the while loop iterates, something like this (obviously, as the values are randomly generated, your values may be different).

Actually, if you double-click on a variable in this window, you can even edit the value! We don’t need to do that here, but keep that one in mind for future debugging.

You can also see variable values just by mousing over them. Hover your mouse over num in your code and you’ll see the last value it got set to (by the random.randrange() call), like this.

Mouse over numbers and you’ll see the list members. You can mouse over variables, functions, and more.

For now, though, keep clicking the Continue button. Each time the Debugger stops look at numbers in the Variables window. Our code is supposed to stop once there are 10 values in numbers. So, keep clicking Continue until … there are more than 10 values in numbers, lots more.

Well, I guess we know why the program ran forever. The while loop never stopped iterating, which means that there’s something wrong with that while condition.

Watching Expressions

Time to use the WATCH window. This one lets you enter expressions and see their results as you step through your code. Click the + at the top of the WATCH window to add this expression:

numbers.count(10)

That’s the same expression we’re using in our while. It should return a count of how many items are in numbers allowing the while loop to terminate once it has 10. But, the expression is evaluating to 0 for us. Odd. Click the Continue button a few times. Does this expression change? Nope, not as expected. So, yeah, something’s wrong.

And, yep! Ugh! We used the wrong function. count() returns the number of list items with a specific value, and as 10 is passed to it as an argument it will only ever return 0 (if there is no 10 in the numbers) or 1 (if there is one). As it won’t return anything else that while loop will run for ever as it will always be < 10. Yes, the dreaded infinite loop!

The function we should have used is len(). We can test that. Add another expression in the WATCH window:

len(numbers)

Now use Continue to keep running your code. The WATCH window will show you that the len() function is indeed returning the value we need. That’s what we should have used as our while condition. Whew, thankfully that’s an easy fix!

Ending Debugging

We found the problem. But before we fix the code, we must end the Debugger session. This is important. If you just go back to the Explorer view to edit your code the Debugger will still be running in the background. We don’t want that.

To stop the Debugger session, click the rightmost button on the Debugger toolbar (the one with a red square). This is the Stop button, and it does just that, it stops the Debugger.

You can then edit the code, fixing the while loop so that it looks like this:

while len(numbers) < 10:

Save the code, run it, make sure the bug has been fixed.

And with that Captain Code can make an impressive exit, never to be seen again. Well, just until the sequel.

Summary

In this bonus chapter we showed you two important goodies; tools and techniques are critical parts of any coding superhero’s bag of tricks. We started with the Python interpreter which provides a wonderful way to tinker and play with functions, expressions, and more. And then we used the powerful Interactive Debugger to step through code while it is executing.