All GCSE 4 40 min read 10 question quiz 19 exam marks

Validation, Authentication and Testing

Writing programs that cope with the wrong input, prove who the user is, can be maintained by somebody else, and are tested with the right data. This is the whole of producing robust programs.

Validation and sanitisation

Defensive design means writing a program that carries on behaving sensibly when people do the wrong thing, and they will. The first line of defence is input validation: checking that data is acceptable before the program uses it.

Validation checks that data is sensible, not that it is true

A validation check will accept an age of 40 for a ten year old, because 40 is a sensible age. Nothing a program can check by itself catches that. Verification is about checking data was entered the way the person meant, for example by typing a password twice.

CheckQuestion it asksExample
RangeIs it between a lowest and a highest value?Age from 0 to 120
LengthIs it a sensible number of characters?Password at least 8 characters
TypeIs it the right data type?Quantity is a whole number
PresenceWas anything entered at all?Name must not be empty
FormatDoes it follow a pattern?A date is dd/mm/yyyy
LookupIs it one of an allowed list?Size is S, M or L

Each check is a small function that returns True or False:

Python
def in_range(n, low, high):
    return low <= n <= high

def right_length(text, low, high):
    return low <= len(text) <= high

def is_whole_number(text):
    return text.isdigit()

def is_present(text):
    return text.strip() != ""

def in_list(value, allowed):
    return value in allowed

print(in_range(15, 0, 120), right_length("abc", 8, 20))      # True False
print(is_whole_number("42"), is_whole_number("4.2"))         # True False
print(is_present("   "), in_list("M", ["S", "M", "L"]))      # False True

A validation loop

Keep asking until the answer is acceptable. The type check has to come before the range check, because int("abc") would crash the program before the range was ever looked at.

Python
feed = iter(["abc", "-5", "200", "15"])      # stands in for the keyboard

age = -1
while age < 0 or age > 120:
    text = next(feed)                        # in the exam: input("Age: ")
    if text.isdigit():
        age = int(text)
        if age < 0 or age > 120:
            print("Age must be between 0 and 120")
    else:
        print("Please type a whole number")
print("Accepted:", age)

Run it and follow the four attempts. "abc" fails the type check. "-5" fails it too, because a minus sign is not a digit. "200" is a whole number but fails the range check. "15" passes both.

Sanitisation

Sanitising input means cleaning it so it is safe and consistent before it is used. Remove stray spaces, fix the case, and strip out anything the program should never receive.

Python
raw = "   aShA  kETHIRI "
clean = " ".join(raw.split()).title()
print(repr(clean))               # 'Asha Kethiri'

The most important reason to sanitise is that an attacker can type program code into a text box. A name field that is pasted into a database query can become an instruction, which is called SQL injection. The SQL topic shows the proper fix, which is never to build a query out of typed text at all.

Validation checks, and what they cannot doSix validation checks, one card each, with a value that is rejected and a value that is accepted. A presence check makes sure something is entered, so an empty entry is rejected. A type check makes sure the data is the right kind, so the letters abc are rejected for an age. A range check keeps a number between limits, so an age of 200 is rejected. A length check makes sure there are enough characters, so pass is too short for a password of eight or more. A format check makes sure the data matches a pattern, so asha dot com is not an email address without an at sign. A lookup check makes sure the value is one of an allowed list, so Funday is not a day of the week. Two cards underneath separate validation from correctness: validation checks data is sensible, not that it is true, so an age of 15 mistyped as 51 passes a range check and is still wrong. Verification checks that data was entered as intended, for example by typing a password twice and comparing.Presencesomething must be entered""AshaTypethe right kind of data"abc"15Rangebetween two limits (0 to 120)20015Lengthenough characters (8 or more)"pass""pass1234"Formatmatches a pattern"asha.com""asha@mail.com"Lookupone of an allowed list"Funday""Friday"1Valid is not the same as trueAge 15 mistyped as 51 is in range,so every check passes and thedata is still wrong.validation checks it is sensible, not that it is trueVerification: typed twicepasswordenter itconfirmand enter it againdo they match?23
  1. Validation checks: presence, type, range, length, format and lookup.
  2. Validation checks that data is sensible. It cannot know whether it is true.
  3. Verification asks the user again, for example with a confirm password box, to catch typing slips.
Validate on the way in, and tell the user what is wrong, not just that something is.

Authentication

Authentication proves that a user is who they say they are. Validation asks "is this input acceptable?" and authentication asks "is this person allowed in?"

MethodHow it worksWeakness
PasswordA secret only the user should knowGuessed, shared or reused
BiometricsA fingerprint or face is checkedCannot be changed if copied
Two factorA password plus a code sent to a phoneNeeds the second device
LockoutThe account locks after a few wrong triesCan be used to lock out the real user

A login with limited attempts, using a dictionary of users:

Python
users = {"asha": "Tr1angle!", "ben": "kEy2024#"}

def check_login(username, password):
    return username in users and users[username] == password

attempts = [("asha", "wrong"), ("asha", "also wrong"), ("asha", "Tr1angle!")]
tries = 0
logged_in = False
while tries < 3 and not logged_in:
    username, password = attempts[tries]
    tries = tries + 1
    if check_login(username, password):
        logged_in = True
    else:
        print("Incorrect, attempt", tries, "of 3")

if logged_in:
    print("Welcome")
else:
    print("Account locked")

The loop stops for two different reasons, a correct login or three failures, and the if after it has to work out which. That is a typical shape for an exam question.

Password rules

A strong password rule is a validation check on the password. Check one property at a time.

Python
def strong_password(password):
    has_digit = False
    has_upper = False
    for ch in password:
        if ch.isdigit():
            has_digit = True
        if ch.isupper():
            has_upper = True
    return len(password) >= 8 and has_digit and has_upper

print(strong_password("password"))      # False
print(strong_password("Tr1angle!"))     # True
Real systems never store the password itself

A database that holds passwords as typed is stolen in full the day it is breached. Real systems store a hash, a one way scrambling of the password, and compare the hash of what was typed. That is covered at A Level.

Authentication: what you know, have and are, and how passwords are keptAuthentication proves who someone is, using three kinds of evidence, one card each: something you know, such as a password or PIN; something you have, such as a phone showing a one time code, a bank card or a key fob; and something you are, such as a fingerprint or a face. Using two of them together, two factor authentication, is far stronger than one. The login flow shows it: the user enters a username and password, a code is sent to their phone, and only then is access granted, so a stolen password alone is not enough. Underneath, a password should never be stored as typed. The system keeps only a hash, the output of a one way function, so password123 is stored as a string of characters such as 5f4dcc3b and the real password is never kept. If the file is stolen, it does not reveal the passwords.Something you know• • • • • • • •password, PINSomething you have482 913codephone, bank card, key fobSomething you arefingerprint, face1Two-factor loginusername +passwordcode sent toyour phoneaccessgranteda stolen password alone is not enoughthe second factor is something an attacker does not have2Stored as a hashpassword123hash function5f4dcc3b5aa765d6...the real password is never kept,so a leak does not reveal it3
  1. Three kinds of evidence: something you know, have or are.
  2. Two factors together are far stronger than one: a stolen password alone is not enough.
  3. Passwords should be stored as hashes, never as typed, so a leak does not reveal them.
Strong authentication is layers. No single one of them is enough alone.

Writing code that can be maintained

Programs are read many more times than they are written, often by a different person, and often by you months later. Maintainability is how easily the code can be understood and changed. The exam wants these named every time:

TechniqueWhat it does for the reader
Meaningful namesvat_rate says what it holds, x does not
CommentsExplain why, so the reader does not have to guess
Indentation and white spaceShows the structure of the loops and decisions
SubprogramsOne job per subprogram, each with a name
ConstantsVAT = 0.2 in one place, not 0.2 in twenty

Both of these give the same answer:

Python
def f(a,b):
    c=a*b*0.2
    return c
x=f(50,2)
print(x)
Python
VAT_RATE = 0.2

def vat_on_order(unit_price, quantity):
    # VAT is charged on the whole order, not on each item
    order_total = unit_price * quantity
    return order_total * VAT_RATE

vat = vat_on_order(50, 2)
print(vat)

Only the second tells you what is going on.

Testing

Testing proves the program works and finds where it does not. Two kinds of testing, at two points:

  • Iterative testing happens during development. Each subprogram is tested as soon as it is written, so a fault is found while only a few lines could be responsible.
  • Final testing, also called terminal testing, happens at the end on the whole program, against the original requirements.

Three kinds of error

ErrorWhat happensExample
SyntaxThe program will not run at allA missing bracket or colon
RuntimeIt starts, then crashesDividing by zero
LogicIt runs and gives the wrong answerWriting > where >= was meant

A logic error is the dangerous one, because nothing tells you. This function is meant to say whether somebody may vote at 18:

Python
def is_adult(age):
    return age > 18

print(is_adult(17), is_adult(30))      # False True, which looks fine
print(is_adult(18))                    # False, but an 18 year old is an adult

Only a test at the boundary finds it.

Types of test data

Imagine a program that accepts a whole number from 1 to 10.

TypeWhat it isTest dataExpected
NormalTypical and valid5, 7Accepted
BoundaryAt the edge of the valid range1, 10Accepted
Boundary, just outsideOne step beyond the edge0, 11Rejected
InvalidThe right type but not allowed-3, 250Rejected
ErroneousThe wrong type altogether"ten", "", 4.5Rejected
Kinds of test dataA number line from minus 3 to 14 for a program that accepts whole numbers from 1 to 10. The accepted band runs from 1 to 10. Normal data, such as 5 and 7, lies inside it. Boundary data is 1 and 10, on the edge and accepted, and 0 and 11, one step outside and rejected. Invalid data, such as minus 2 and 13, is the right type but outside the range and is rejected. Erroneous data is the wrong type altogether, such as the word ten, 4.5 or an empty entry, and is rejected.A program that accepts whole numbers from 1 to 10ACCEPTED-3-2-10123456789101112131457110011-213Normaltypical valid data: 5, 7Boundarythe edge, and one step outside: 1, 10 and 0, 11Invalidthe right type, not allowed: -2, 13, 250Erroneousthe wrong type: "ten", 4.5, an empty entry
Test both sides of every edge. Most off by one errors hide on the boundary.
Boundary data means two values at each edge

The value on the boundary, which is valid, and the value just beyond it, which is not. Most logic errors, like the > that should have been >=, hide exactly there.

A test plan, run by the computer

A test plan has the test data, what you expect, what actually happened and a pass or fail. Python can run the plan for you, which is how professionals do it.

Python
def valid_age(text):
    if not text.isdigit():
        return False
    return 0 <= int(text) <= 120

tests = [
    ("normal", "15", True),
    ("normal", "60", True),
    ("boundary", "0", True),
    ("boundary", "120", True),
    ("boundary", "121", False),
    ("invalid", "150", False),
    ("erroneous", "abc", False),
    ("erroneous", "12.5", False),
    ("erroneous", "", False),
]

failed = 0
for kind, data, expected in tests:
    actual = valid_age(data)
    if actual == expected:
        result = "pass"
    else:
        result = "FAIL"
        failed = failed + 1
    print(kind.ljust(10), repr(data).ljust(7), expected, actual, result)
print(failed, "tests failed")
What a test table must have

A test number, the purpose, the test data, the expected result and the actual result. Expected is worked out from the specification before you run anything. If you write expected after seeing the output, you are not testing, you are copying.

Key terms you must be able to define

Validation
Checking that data entered is reasonable and allowed, such as within a range, before the program uses it.
Verification
Checking that data has been entered as the user intended, such as typing a password twice.
Sanitisation
Cleaning input so that it is safe and consistent, by removing unwanted characters and fixing its form.
Authentication
Proving that a user is who they claim to be, for example with a password or a fingerprint.
Maintainability
How easily a program can be understood, corrected and improved by somebody else.
Iterative testing
Testing each part of a program during development, as soon as it is written.
Boundary data
Test data on the edge of what is allowed, and the value just beyond it.
Erroneous data
Test data of the wrong type, which the program should reject.
Logic error
A fault that lets a program run but makes it give the wrong result.

Flashcards

Say the definition out loud before you flip the card. Retrieving it from memory is what builds recall, reading it again is not.

DefineValidation
AnswerChecking that data entered is reasonable and allowed, such as within a range, before the program uses it.
DefineVerification
AnswerChecking that data has been entered as the user intended, such as typing a password twice.
DefineSanitisation
AnswerCleaning input so that it is safe and consistent, by removing unwanted characters and fixing its form.
DefineAuthentication
AnswerProving that a user is who they claim to be, for example with a password or a fingerprint.
DefineMaintainability
AnswerHow easily a program can be understood, corrected and improved by somebody else.
DefineIterative testing
AnswerTesting each part of a program during development, as soon as it is written.
DefineBoundary data
AnswerTest data on the edge of what is allowed, and the value just beyond it.
DefineErroneous data
AnswerTest data of the wrong type, which the program should reject.

Reach the top grade on this topic

Top marks on robust program questions are for exact terms and for test data that would really find a fault.

  • Name the check: range, length, type, presence, format or lookup, and write the condition for it
  • Do the type check first, so the range check never receives data it cannot compare
  • Give boundary data as a pair at each edge: the last valid value and the first invalid one
  • State the expected result before the actual result in every test table
  • Say why, not just what: meaningful names help because another programmer must understand the code

Mistakes that cost marks here

Avoid these
  • Saying validation checks that data is correct, rather than reasonable.
  • Choosing only one value from the boundary, so the off by one bug is never exposed.
  • Mixing up invalid data, which is the right type but not allowed, with erroneous data, which is the wrong type.
  • Writing a test plan with no expected result, or one worked out after running the program.
  • Describing maintainability as making the program faster, which it is not.
Fun fact

The word bug has been used for a technical fault since at least the 1870s, long before computers, when Thomas Edison used it in letters about his inventions. The famous 1947 moth in a Harvard computer was a joke on a word already in use.

Knowledge check

0 of 10 answered
1What does validation check?
2Which validation check is len(password) >= 8?
3Which check makes sure something has been entered at all?
4A program accepts whole numbers from 1 to 10. Which value is boundary test data?
5A program expects a whole number from 1 to 10. Which is erroneous test data?
6What is the difference between validation and verification?
7What is authentication?
8What is a logic error?
9When does iterative testing take place?
10Which of these makes code easier to maintain?
Score 0 / 10

Exam-style questions

Five written questions in the style of the real paper. Write a full answer, then let the marker check it against the mark scheme.

19 marks
1Explain the difference between validation and verification.3 marks
Explain

Mark scheme0 / 3
  • LValidation checks that data is reasonable or allowed
  • LVerification checks that data was entered as the user intended
  • LValidation cannot tell whether the data is actually correct
Model answer

Validation is an automatic check that the data entered is reasonable and allowed, such as a number being in range, but it cannot tell if the data is actually correct, because a wrong age that is still between 0 and 120 will pass. Verification checks that the data was entered as the user intended, for example by asking for a password to be typed twice and comparing the two.

The marker looks for key phrases. If you said it another way and it is still right, give yourself the mark.
2Write a Python function called good_password that takes a password and returns True if it is at least 8 characters long and contains at least one digit, and False otherwise.4 marks
Write

Mark scheme0 / 4
  • LDefines a function with a parameter for the password
  • LChecks the length is 8 or more using len
  • LLoops through the characters checking for a digit, such as with isdigit
  • LReturns True only when both conditions are met
Model answer
Python
def good_password(password):
    has_digit = False
    for ch in password:
        if ch.isdigit():
            has_digit = True
    return len(password) >= 8 and has_digit

print(good_password("Tr1angle!"), good_password("password"), good_password("a1"))

The loop sets a flag if any character is a digit. The function returns True only when the length check and the digit check are both true. The calls shown print True False False.

The marker looks for key phrases. If you said it another way and it is still right, give yourself the mark.
3A program asks the user to enter their test mark, which must be a whole number from 0 to 100 inclusive. Give one item of normal data, one item of boundary data, one item of invalid data and one item of erroneous data that could be used to test it. State whether each should be accepted.4 marks
Give

Mark scheme0 / 4
  • LNormal data accepted, such as 50
  • LBoundary data such as 0 or 100 accepted, or 101 or -1 rejected
  • LInvalid data such as 150 or -20 rejected
  • LErroneous data such as a word or a decimal rejected
Model answer

Normal data, for example 50, should be accepted. Boundary data is on the edge of the range: 0 or 100 should be accepted, and the values just outside, -1 or 101, should be rejected. Invalid data is a whole number outside the range, such as 150, and should be rejected. Erroneous data is the wrong type altogether, such as the word ten or the decimal 7.5, and should be rejected.

The marker looks for key phrases. If you said it another way and it is still right, give yourself the mark.
4A login system gives users three attempts to enter the correct password. Describe two other ways that the system could be made more secure.4 marks
Describe

Mark scheme0 / 4
  • LTwo factor authentication such as a code sent to a phone
  • LExplains that a stolen password alone is then not enough
  • LRules for password strength such as minimum length, digits or capitals
  • LExplains that stronger passwords are harder to guess
Model answer

Two factor authentication asks for something in addition to the password, such as a code sent to the user's phone, so a person who has stolen or guessed the password still cannot log in without the second device. Enforcing password rules, such as a minimum length and a mix of capitals, digits and symbols, makes the password much harder to guess or to crack by trying many combinations.

The marker looks for key phrases. If you said it another way and it is still right, give yourself the mark.
5The program shown works but is difficult to maintain. Explain two changes that would make it easier to maintain.4 marks
Explain
Python
def f(a,b):
    c=a*b*0.2
    return c
x=f(50,2)
print(x)

Mark scheme0 / 4
  • LUse meaningful names for the function, parameters and variables
  • LSo that another programmer can tell what each one is for
  • LReplace 0.2 with a named constant, or add comments and white space
  • LSo the value is only changed in one place, or the reader knows why the code is there
Model answer

The names f, a, b, c and x say nothing about what the program does, so they should be replaced with meaningful names such as calculate_vat, price, quantity and vat, so another programmer can tell what each is for without working it out. The number 0.2 is unexplained, so it should be stored in a named constant such as VAT_RATE, which explains it and means it only has to be changed in one place if the tax rate changes. Comments and white space around the operators would also make the code easier to follow.

The marker looks for key phrases. If you said it another way and it is still right, give yourself the mark.
Machine mark 0 / 19