8
1 of 8 Teaching guide: Testing This resource will help with understanding of robust and secure programming through testing. It supports Section 3.2.11 of our GCSE Computer Science specification (8525). The guide is designed to address the following learning aims: Understand the essential need to test algorithms and programs. Identify test data that can be used to increase the effectiveness of test results (such as data at the boundaries of acceptance and erroneous data). Test plans Developers test algorithms and programs all the time. Quite often this is informal testing run the program and check that it works and, if not, find out where the errors are. Testing is, of course, a vital step in ensuring that our algorithms and programs are correct. It is, however, not always straightforward to know which tests to perform to accomplish this. Take this example from an earlier teacher resource: setters_number USERINPUT guess USERINPUT IF setters_number = guess THEN OUTPUT well done!ELSE OUTPUT bad luck!ENDIF There are two possible pathways through this algorithm the path for which the Boolean condition is True and the one for which it is False. To ensure that this algorithm is correct we will choose two sets of test data - one where setters_number and guess are equal and another where they are not equal. Test results should be logged and the obvious way to do this is by using a table. © AQA 2020

Teaching guide: Testing - AQA

  • Upload
    others

  • View
    3

  • Download
    0

Embed Size (px)

Citation preview

1 of 8

Teaching guide: Testing

This resource will help with understanding of robust and secure programming

through testing. It supports Section 3.2.11 of our GCSE Computer Science

specification (8525). The guide is designed to address the following learning aims:

Understand the essential need to test algorithms and programs.

Identify test data that can be used to increase the effectiveness of test

results (such as data at the boundaries of acceptance and erroneous

data).

Test plans

Developers test algorithms and programs all the time. Quite often this is informal

testing – run the program and check that it works and, if not, find out where the errors

are.

Testing is, of course, a vital step in ensuring that our algorithms and programs are

correct. It is, however, not always straightforward to know which tests to perform to

accomplish this.

Take this example from an earlier teacher resource:

setters_number ← USERINPUT

guess ← USERINPUT

IF setters_number = guess THEN

OUTPUT ꞌwell done!ꞌ

ELSE

OUTPUT ꞌbad luck!ꞌ

ENDIF

There are two possible pathways through this algorithm – the path for which the

Boolean condition is True and the one for which it is False. To ensure that this

algorithm is correct we will choose two sets of test data - one where

setters_number and guess are equal and another where they are not

equal.

Test results should be logged and the obvious way to do this is by using a table.

© AQA 2020

2 of 8

We can fill in most of the table before the tests are carried out:

Test Number

Description Input Data Expected Outcome

Result

1

Test for the correct

outcome when

setters_number

and guess are

equal

setters_number

has the value 1

guess has the

value 1

OUTPUT 'well

done!'

is executed.

2

Test for the correct

outcome when

setters_number

and guess are not

equal

setters_number

has the value 1

guess has the

value 2

OUTPUT 'bad

luck!'

is executed.

When the tests are run the last column can be filled in and, assuming the

implementation of this algorithm is correct, the completed test table will look like this:

Test Number

Description Input Data Expected Outcome

Result

1

Test for the correct

outcome when

setters_number

and guess are

equal

setters_number

has the value 1

guess has the

value 1

OUTPUT 'well

done!'

is executed.

PASSED

2

Test for the correct outcome when setters_number

and guess are not

equal

setters_number

has the value 1

guess has the

value 2

OUTPUT 'bad

luck!'

is executed.

PASSED

Two tests suffice for this algorithm because there are only two paths through the

algorithm and, as the algorithm is so small and so easy to follow, if the algorithm

passes these two tests we can be fairly confident that it will work in all other cases.

More commonly though, testing is not such a straightforward exercise.

We will look at two further examples which both require more substantial testing

before lastly looking at a way to automate testing within our programs.

© AQA 2020

3 of 8

Choosing the Test Data

Consider the following example to allow user input of a number between certain

boundaries:

TRY

# type check (will jump to CATCH if it fails)

guess ← STRING_TO_INT(guess_as_string)

# range check

IF guess < 1 OR guess > 100 THEN

OUTPUT 'Must be between 1 and 100'

ELSE

# if all checks passed then input is valid

valid ← True

ENDIF

CATCH

OUTPUT 'Must enter an integer (e.g. 42)'

ENDTRY

This is a more complex example that validates a user’s input and consequently the

test plan requires some more thought. Firstly, let’s create a list of the purposes of

this algorithm:

the user should enter an integer

the integer should be between 1 and 100 inclusive (i.e. 1 and 100 are permitted but

0 and 101 are not)

From here you can think about test data that would make you confident that this

algorithm is correct. We can’t be exhaustive and test every single possible input in

our testing for the simple reason that the input to this algorithm are strings that

represent integers and there are an infinite number of integers. As it is not possible

to perform an infinite number of tests then we need to carefully select representative

test data.

Programmers frequently make logical errors at the boundaries of what should and

should not be accepted. Using the example above, this would mean at the lowest

possible value that should be accepted and then the value below that, and the

highest possible value and then the value above that. All of these items of data are

called boundary data, the data that should not be accepted is additionally called

erroneous data and together this data forms a range check on our program.

Populating a test table with this boundary data gives us:

© AQA 2020

4 of 8

Test Number

Description Input Data Expected Outcome Result

1

Test for the highest acceptable integer value (boundary)

'100' The value should be

accepted with no

output

2

Test for the integer one above the top range (boundary, erroneous)

'101' The value should not be accepted and the algorithm should output 'Must be between 1

and 100' and prompt

the user to re-enter a value

3

Test for the lowest acceptable integer value (boundary)

'1' The value should be

accepted with no

output

4

Test for the integer one below the bottom range (boundary, erroneous)

The value should not be accepted and the

algorithm should output

'Must be between

1 and 100' and

prompt the user to re-

enter a value

In addition to these values you also have to test for more obscure user input. The

input must be a string that can be converted to an integer so you should perform a

type check and see if the algorithm performs correctly when a value such as

'50.1' or 'Fifty' is entered. You could also check that the algorithm rejects

the empty string as input (i.e. when the user enters nothing) – this is known as a

presence check.

Test Number

Description Input Data Expected Outcome Result

5

Test that non-integer

data is rejected

(erroneous)

'50.1' The value should not be accepted and the algorithm should output

'Must enter an

integer (e.g.

42)'

6

Test that non-integer

data is rejected

(erroneous)

'Fifty' The value should not be accepted and the algorithm should output

'Must enter an

integer (e.g.

42)'

© AQA 2020

5 of 8

7

Test that empty user

input is rejected

(erroneous)

'' This should not be accepted and the algorithm should output

'Must enter an

integer (e.g.

42)'

Finally, we should enter some normal data to check that the algorithm works with normal,

typical data that should be accepted:

Test Number

Description Input Data Expected Outcome Result

8

Test that normal data is accepted (normal)

'50' The value should

be accepted with

no output

9

Test that normal data is accepted (normal)

'12' The value should

be accepted with

no output

Nine separate tests have been defined to get to the point at which you can be highly

confident that our algorithm performs as expected. In another resource we covered

implementing validation as extensible subroutines, hopefully now you can appreciate

why this not only reduces the amount of written code in a program that contains

significant amounts of similar validation but also greatly speeds up the testing

process.

In summary, these are the types of test you have conducted:

Type of Test Meaning

Range check To test that the algorithm performs differently depending on whether the input values are within a given range

Type check To test that the algorithm responds accordingly if the input is of an incorrect type

Presence check To test that the algorithm responds accordingly if the input is nothing

And this is the type of test data we have used:

Type of Test Data Meaning

Boundary data Values at the extremes of what should and shouldn’t be accepted

Erroneous data Data that, for whatever reason, is incorrect

Normal data Correct, typical data

© AQA 2020

6 of 8

There are many other types of testing that need to be performed, particularly as the

algorithms and programs you develop become larger, such as systems testing and

integration testing, but these are beyond the requirements of the specification.

Unit testing (not in the specification)

This resource ends with a skill that pulls together elements of subroutines, structural

programming and testing. It is highly dependent on the language and possibly the

programming environment that you are using to develop your programs but familiarity

with unit testing will greatly increase your speed and confidence in developing

programs that work.

Previous resources have covered subroutines and the benefits of modularising

program development; decomposing a problem into self-contained subroutines until

each module is focused enough to become a solution in its own right.

Each of these subroutines can be tested individually using the type of tests and test

data we have just encountered. However, it is possible to write these tests directly

into the program you are developing and have the tests run automatically when your

program starts.

An example of this can be seen in the subroutine below from a gardening example:

SUBROUTINE calculate_quote(length, width, turf)

quote = 0.0

RETURN quote

ENDSUBROUTINE

At this point in development, you could create a test plan for this subroutine that

could look like this (note that the point here is not to test the format of the inputs, but

to test that the calculation that the subroutine performs is correct):

Test Number

Description Input Data Expected Outcome Result

1

Test that quote is calculated correctly using the formula length * width

* turf * 1.5

length has the

value 2.0

width has the

value 3.0

turf has the

value 10.0

2.0*3.0*10.0*1

.5 = 90.0

2

Test that quote is calculated correctly using the formula length * width

* turf * 1.5

length has the

value 1.1

width has the

value 2.2

turf has the

value

3.3

1.1*2.2*3.3*1.

5 = 11.979

© AQA 2020

7 of 8

You could call this subroutine twice with these parameters and output the result, or

you could create a new subroutine that performs these two tests like so:

SUBROUTINE unit_tests()

test_1 ← calculate_quote(2.0, 3.0, 10.0)

IF test_1 ≠ 90.0 THEN

OUTPUT 'calculate quote test 1 failed'

RETURN False

ENDIF

test_2 ← calculate_quote(1.1, 2.2, 3.3)

IF test_2 ≠ 11.979 THEN

OUTPUT 'calculate quote test 2 failed'

RETURN False

ENDIF

RETURN True

ENDSUBROUTINE

Calling this subroutine at the start of the program will quietly perform the test and, if

they both pass, will output nothing and return the value True. However, if either of

these tests fail then an error message will be displayed and the subroutine will return

the value False.

Apart from being a targeted way to test individual subroutines, this style of testing is

embedded in your program throughout the development process and is called every

time your program runs which avoids manually repeating tests.

A final observation on unit tests, which is less obvious, is ensuring that they do fail.

As these tests are silent, if successful, you need to be convinced that they are

actually working. To accomplish this, you could write your unit tests before you

implement the subroutine that you are testing. If you run your program with only the

skeleton of the subroutine written then the test should fail – if it doesn’t then you have

made an error in your test! A slimmed down version of the program in development

could look like this:

# more subroutines as skeleton code

SUBROUTINE calculate_quote(length, width, turf)

quote = 0.0

RETURN quote

ENDSUBROUTINE

# more subroutines as skeleton code

SUBROUTINE unit_tests()

© AQA 2020

8 of 8

test_1 ← calculate_quote(2.0, 3.0, 10.0)

IF test_1 ≠ 90.0 THEN

OUTPUT 'calculate quote test 1 failed'

RETURN False

ENDIF

test_2 ← calculate_quote(1.1, 2.2, 3.3)

IF test_2 ≠ 11.979 THEN

OUTPUT 'calculate quote test 2 failed'

RETURN False

ENDIF

RETURN True

ENDSUBROUTINE

# call the test subroutine and expect it to fail

tests_passed ← unit_tests()

Once you have checked that the unit tests work as expected, you can develop the

calculate_quote subroutine, run the program again and – if you don’t

receive any test failure messages – be confident that you have implemented it

correctly (assuming you have devised the tests correctly!).

© AQA and its licensors. All rights reserved