Chapter 8: Testing and Troubleshooting

In general, you should be using Test-Driven Development, especially for applications of substantial size. It helps to make your program more reliable and defect-free. With nearly 100 per cent test coverage, TDD is practically the only way to ensure program correctness. This is especially important given that Smalltalk is dynamically typed, rather than statically typed. Static typing purports to aid in programming safety. However, the use of TDD can completely eliminate this advantage.

Since Cranky is a very small and simple application, TDD is unnecessary. Nevertheless, it’s good practice to use it here. I show you an example with the #FFICranky class.

TestCase subclass: #FFICrankyTest
instanceVariableNames: ''
classVariableNames: ''
package: 'Cranky'
FFICrankyTest»testGetSysname
self assert: FFICranky uniqueInstance getSysname equals: 'Linux'
FFICrankyTest»testGetCpuTemp
self assert: FFICranky uniqueInstance getCpuTemp > 5.0

Note that if the CPU temperature is really as low as 5.0° Celsius, you must be working in a freezer!

Debugging tools

Diagnosing software defects, or bugs, is perhaps the most difficult task in programming. It will probably eat up more development time than any other activity, including requirements analysis, program design, and coding. (Program testing is usually performed by a group independent of the developers, because developers are too close to the software and may exhibit hidden biases. Testing is usually very time-consuming.)

The two most common and effective tools for debugging are the print statement and the debugger utility. I favour the print statement because it is readily available for all programming languages and it is very, very easy to use. A debugger requires some training to use properly.

While the Smalltalk debugger is not difficult to use, it is beyond the scope of this tutorial to demonstrate how to use it. But you can still use the trusty ol’ print statement! Use it to print out the values of variables. Use it to trace the execution path. In this way, you can better understand what is going on in your program.

[There is now a separate tutorial for How to use the Pharo Debugger.]

Stress testing

You should always stress test your application. Run it under as many different conditions as you can imagine. Run it for long periods of time, even overnight. Run it repeatedly by closing and restarting the application. Try all combinations of user input (this doesn’t apply to Cranky since it doesn’t have a UI or user interface). Run it under heavy processor load. And so on.

Once you’re satisfied that the program is solid, turn it over to your testing group (aka Quality Assurance). They’ll certainly come up with issues to flummox you!

A Teachable Moment

In my first version of Cranky, I used an endless loop running in a separate thread to update the Morphic window. From time to time, the Cranky image would crash. This occurred if you repeatedly ran Cranky, closed Cranky, saved and exited the image, and restarted the image.

An investigation into the problem suggested that I couldn’t use a separate thread because Morphic wasn’t “thread-safe” (meaning that it couldn’t guarantee safety if more than one thread accessed it simultaneously).

The fact that Cranky had intermittent (though infrequent) and unpredictable crashes provides an opportunity to discuss a strategy for investigating difficult-to-diagnose problems.

When confronted with such a bug, I always try to simplify the code to the barest minimum: remove as much code as I can to get to a very simple scenario. That way, I can be certain I understand what the problem is, or why the bug occurs.

For this particular bug, I simplified to the following code in Cranky’s #initialize method:

Cranky»initialize
f := Form fromFileNamed: 'hot_air_balloon_mysticmorning.jpg'.
m := ImageMorph new.
m form: f.
m openInWindowLabeled: 'Cranky'.
delay := Delay forSeconds: 5.
[ [ true ] whileTrue: [ delay wait ] fork

Thus, when I run Cranky, all I get is the ImageMorph displayed in a window and a do-nothing thread executing an infinite loop with a 5-second delay. In this scenario, Cranky does not appear to crash. (I ran the test about 20–30 times. If it hasn’t failed by then, it’s probably good.)

However, if in the thread I simply change an info field with a harmless string, Cranky exhibits the intermittent crashes:

"Create an instance variable 'a' first"
Cranky»initA
a := ((StringMorph contents: '####')
color: Color white) position: 0@0.
m addMorph: a
Cranky»initialize
f := Form fromFileNamed: 'hot_air_balloon_mysticmorning.jpg'.
m := ImageMorph new.
m form: f.
self initA.
m openInWindowLabeled: 'Cranky'.
delay := Delay forSeconds: 5.
[ [ true ] whileTrue: [ a contents: '0'. delay wait ] fork

The key point here is that this scenario is so simple, nothing should go wrong. But something does! Clearly, my infinite loop and Morphic don’t mix.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store