Software
Development Practices
Continued… Yes there are a lot of components to dev work
Common Tooling
● Package Manager
● Virtual Environments
● Version Control
● Task Runners
● Formatters
● Test Frameworks
● …and then there are tools used in particular contexts.
We will look at tooling in the context of python application development.
Version Control
A way of storing the state of files over time, and returning them to how
they were at different points in time.
Why use Version Control?
● If you’ve made a mistake, you can always go back
● History Tracking: Every change is recorded, making it easy to see what changed,
when, and by whom.
● To go back and copy good ideas, or to copy good ideas back
● Disaster Recovery: Provides a robust way to revert to previous states or recover
lost work.
ALSO: for collaboration between people! (but we’ll get to that later)
Git
● What is Git? An open source distributed version control system
● How it Works:
○ Git takes "snapshots" of your entire project at specific
moments.
○ These snapshots are called commits, and they form a linear
history.
● Distributed System: Every developer has a full copy of the entire
repository history on their local machine.
Task Runner
In most ecosystems, a task runner can…
● Create a new project, using a pre-built template
● Build a project, using other tools to perform the necessary steps
● Publish a project in final form, with final optimizations
● Manage project dependencies, often using other tools
● Run a project
● Check a project, using a test framework
In some ecosystems, the task runner does a lot.
In other ecosystems, the task runner doesn’t do much.
Formatters
● Code formatters are tools or programs that automatically format your source code
according to predefined style guidelines and conventions.
● They help developers ensure consistent and clean code by automatically applying
consistent:
○ Indentation
○ Spacing
○ line breaks
○ and other formatting rules.
● By automating formatting, teams ensure that all code adheres to the same
standard, reducing cognitive load and making code reviews faster because they can
focus on logic, not style.
● A good formatter is idempotent; once your code is formatted, running the
formatter again will cause no further changes.
The Uncompromising Code Formatter
● Black is the uncompromising Python code formatter.
○ Often called "opinionated" because it offers very few configuration options.
○ It enforces one specific style, which is its main benefit—it ends the debate entirely.
● Black is a PEP 8 compliant opinionated formatter with its own style
● PEP 8 - Style Guide for Python Code
○ Document gives coding conventions for the Python code
○ Code is read much more often than it is written -> improve the readability of code
○ Focus is on consistency
● pip install black
Test Framework
A test framework consists of at least the following:
● Software libraries/packages that make it easy to write tests
○ Unit tests, which test discrete and isolated functionality
○ Integration testing, which test system functionality
○ Mocking
● A test runner that executes tests and reports the results
Unit Tests
Unit tests are written using the pattern Arrange-Act-Assert.
● We arrange for a particular environment to be in place
● We act by calling the function/method that we want to test
● We assert that the result is what we expect it to be
If an assertion (step 3) fails, then the test fails. Otherwise, the test passes.
First principles
Unit tests should be:
● Fast
○ Execute quickly, each test only takes a few milliseconds to run
● Independent / Isolated
○ A test must not depend on the result of any other test
○ A test should not depend on any other test being executed before it
● Repeatable
○ Must reliably give the same result, no matter when or where it is executed
● Self-checking
○ A test must decide pass/fail by itself using clear assertions, with no human
judgment or manual inspection
● Thorough
○ Cover all likely cases and as many edge-cases as possible
Testing a Function with the Arrange-Act-Assert Pattern
def get_initials(full_name: str) -> str:
""" Takes a full name and returns the initials. """
if not full_name or not isinstance(full_name, str):
return ""
parts = full_name.split()
if not parts:
return ""
initials = [part[0].upper() for part in parts]
return "".join(initials)
The Pattern in Action: A Complete Example
def test_get_initials_for_standard_name():
# Arrange
# Set up the inputs and expected result.
full_name_input = "Sipho Khumalo"
expected_outcome = "SK"
# Act
# Execute the code being tested.
actual_result = get_initials(full_name_input)
# Assert
# Verify the actual result matches the expected result.
assert actual_result == expected_outcome
Integration tests
● Whole-system tests
● Check overall system functionality
Add that is a brief introduction to
Software Development Practices
Questions?