Saturday, 13 October 2018

Testing a program

Once a program reaches a certain size, and maybe has a few different functions and options, making sure it does what you intended becomes a task in itself. When you reach the level of professional software for public release, testing becomes an integral part of the process.
As anyone who has encountered bugs in a program they are using, even the professionals don't always get every bug - often because they are not aware of the bug before the software is released to the public.
Before now my programs have been short enough that the range of inputs is fairly simple.
As previously discussed, the two main groups of bugs are syntax errors, where the Python Interpreter does not like what is written (one bracket in a pair is missing, or there isn't a colon at the end of the first line of a while, for or if structure, or a string isn't enclosed in quotes, so the program thinks it's a variable), and logical errors where the programmer gives the Python interpreter valid instructions but the results are not what the programmer wants.
Syntax errors in the main program are usually picked up quickly - a lot will be found when I first try to run the code and IDLE points out the errors that prevent it from doing so, usually the first one encountered when it tries to run the program, and so multiple runs and debugs will work through these syntax errors.
Some syntax errors are not exactly within Python but with another software package that Python interacts with - for example if I am using Python to interact with SQLite and I tell the Python cursor object to execute a bad SQLite command, SQLite will send an error back to Python and Python in turn will interrupt the program with this error.
Some syntax errors are more subtle, particularly if they are within functions. These may only appear once the program calls on the function. For example there is an SQLite error because one function (here searchtable()) sends a bad SQLite command via the cursor:
========= RESTART: C:\Users\pc\Documents\Programming\historical_4.py =========
Historical Figures Table
========================
Main menu - Please select an option
View whole table ---V
Search table -------S
Enter new record ---E
Delete record ------D
Update record ------U
Quit Program -------Q
Add/Delete Role ----R
Add/Delete Weblink -W
? s
Searching table - please select which column
Name ----(N)
Born ----(B)
Died ----(D)
Nation --(T)
Role ----(R)
? r
What Role? explorer
Traceback (most recent call last):
  File "C:\Users\pc\Documents\Programming\historical_4.py", line 336, in <module>
    searchtable()
  File "C:\Users\pc\Documents\Programming\historical_4.py", line 240, in searchtable
    curs.execute("SELECT * FROM historicalfigures WHERE " + searchterm +";")
sqlite3.Warning: You can only execute one statement at a time.
>>>
This particular one turned out to be an unwanted semicolon, making SQLite think there were 2 commands rather than the one intended.
From my own experience, the best way, perhaps only way, to find these errors is to thoroughly test the program, systematically going through each function and trying different inputs, first valid inputs that the programmer expects from the user, and then (if feeling adventurous), invalid or wrong inputs. Ideally the program should not crash simply because the user makes a mistake.
When doing this I will sometimes generate records that are not intended to be kept - for example adding Mickey Mouse as a historical figure. This is for two reasons - Firstly if I get things wrong (in terms of the program parsing my input incorrectly) then any problematic records can be deleted without regret. Secondly it means I am motivated to use the delete functions, which I use less often than the entry/adding functions.

As an aside, some files and databases may be permanently changed as a result of programs being run, such as this historical database being changed when testing the adding, editing and deleting of records. If the data is important, it may be better to create a test copy of the data and test the program using that, or at least create a backup of both the original data and the original program. Here one of SQLite's features is useful - unlike some SQL databases, SQLite keeps each database in a single file that can be copied and pasted like other files.
I'm not going to go through each error I have discovered in the historical database program - it turns out there were a lot more than I had expected.

Another aspect of testing is seeing things from a user's point of view. While testing this historical database program I have sometimes thought "Wouldn't it be convenient if...." or "Wouldn't it be cool if...". These can give rise to improvements to the program. Those improvements will need testing in due course to ensure new errors have not been introduced.

No comments:

Post a Comment