Stage 1 of 4, lesson 6 of 31. Beginner
Test your own functions with plain asserts
Write down what a function must do as small checks that run in a second, so a later change cannot break it unnoticed. It introduces assert, test functions and testing that an error is raised.
Risk warning: CFDs and spread bets are complex instruments and come with a high risk of losing money rapidly due to leverage. You can lose money on these products, and each provider publishes its own loss figure under FCA rules. You should consider whether you understand how they work and whether you can afford to take the high risk of losing your money. Automated and API trading can amplify losses through bugs, connectivity failures and runaway orders.
You need: The lesson on functions. No broker account.
You already test your code: you run it and look at the answer. A written test is the same act, kept. It states an input and the answer you expect, and it complains only when the two disagree.
Python has the tool built in. The word assert is followed by something that must be true. When it is true nothing happens. When it is false the program stops with an AssertionError. A test is a small function holding one or two asserts, named for the thing it proves.
Good tests go for the places where code breaks: the other direction, the zero, the input that should be refused. The script tests a profit function four ways, runs the tests and counts the passes, then shows what a failure looks like.
When you have more than a handful of tests, a tool called pytest finds and runs functions named like these for you. The asserts stay the same.
The code
# Write tests for your own functions with plain asserts.
# No broker, no account, no internet, and no test framework: assert is part of Python.
def profit(direction, entry, exit_price, size):
"""Profit or loss on one closed trade: points moved in your favour, times size."""
if direction not in ("BUY", "SELL"):
raise ValueError("direction must be BUY or SELL")
points = exit_price - entry if direction == "BUY" else entry - exit_price
return round(points * size, 2)
# Each test is a small function that states one thing that must be true.
def test_buy_that_rises_makes_money():
assert profit("BUY", 100.0, 104.5, 2) == 9.0
def test_sell_that_rises_loses_money():
assert profit("SELL", 100.0, 104.5, 2) == -9.0
def test_no_move_is_zero():
assert profit("BUY", 100.0, 100.0, 5) == 0.0
def test_bad_direction_is_refused():
try:
profit("HOLD", 100.0, 101.0, 1)
except ValueError:
return # the error we wanted
assert False, "a bad direction should raise ValueError"
tests = [
test_buy_that_rises_makes_money,
test_sell_that_rises_loses_money,
test_no_move_is_zero,
test_bad_direction_is_refused,
]
failed = 0
for test in tests:
try:
test()
print("pass", test.__name__)
except AssertionError as problem:
failed += 1
print("FAIL", test.__name__, problem)
print(len(tests) - failed, "of", len(tests), "tests passed")
# What a failing assert looks like. This one expects the wrong answer on purpose.
try:
got = profit("BUY", 100.0, 104.5, 2)
assert got == 10.0, "expected 10.0, got " + str(got)
except AssertionError as problem:
print("A failing assert says:", problem)What you should see
pass test_buy_that_rises_makes_money pass test_sell_that_rises_loses_money pass test_no_move_is_zero pass test_bad_direction_is_refused 4 of 4 tests passed A failing assert says: expected 10.0, got 9.0
Run with Python 3.12 on Windows. No network and no account.
Check yourself
Answer each question from the code above before you open it. The answers were written from the script on this page and what it printed.
What does assert do when the thing after it is false?
It raises an AssertionError. If a message follows a comma, as in the last block, the error carries it. The script prints: expected 10.0, got 9.0.
How does test_bad_direction_is_refused pass?
It calls profit with HOLD, which raises ValueError. The except catches it and returns, so the test ends there. The assert False line is reached only if no error was raised.
Why is there a test for a SELL when a BUY is already tested?
The two directions take different sides of the if inside profit. A function can be right for one and wrong for the other, and the same move gives 9.0 one way and -9.0 the other.
Exercise
Add a fifth test, test_sell_that_falls_makes_money, that expects a SELL of size 4 from 100.0 down to 97.5 to make 10.0. Add it to the tests list and run the script.
What the changed script prints
pass test_buy_that_rises_makes_money pass test_sell_that_rises_loses_money pass test_no_move_is_zero pass test_bad_direction_is_refused pass test_sell_that_falls_makes_money 5 of 5 tests passed A failing assert says: expected 10.0, got 9.0
Captured from a run of the solution on . Run with Python 3.12 on Windows. No network and no account.
One solution
# Exercise solution: the lesson script with the one change the exercise asks for.
# Write tests for your own functions with plain asserts.
# No broker, no account, no internet, and no test framework: assert is part of Python.
def profit(direction, entry, exit_price, size):
"""Profit or loss on one closed trade: points moved in your favour, times size."""
if direction not in ("BUY", "SELL"):
raise ValueError("direction must be BUY or SELL")
points = exit_price - entry if direction == "BUY" else entry - exit_price
return round(points * size, 2)
# Each test is a small function that states one thing that must be true.
def test_buy_that_rises_makes_money():
assert profit("BUY", 100.0, 104.5, 2) == 9.0
def test_sell_that_rises_loses_money():
assert profit("SELL", 100.0, 104.5, 2) == -9.0
def test_no_move_is_zero():
assert profit("BUY", 100.0, 100.0, 5) == 0.0
def test_bad_direction_is_refused():
try:
profit("HOLD", 100.0, 101.0, 1)
except ValueError:
return # the error we wanted
assert False, "a bad direction should raise ValueError"
def test_sell_that_falls_makes_money():
assert profit("SELL", 100.0, 97.5, 4) == 10.0
tests = [
test_buy_that_rises_makes_money,
test_sell_that_rises_loses_money,
test_no_move_is_zero,
test_bad_direction_is_refused,
test_sell_that_falls_makes_money,
]
failed = 0
for test in tests:
try:
test()
print("pass", test.__name__)
except AssertionError as problem:
failed += 1
print("FAIL", test.__name__, problem)
print(len(tests) - failed, "of", len(tests), "tests passed")
# What a failing assert looks like. This one expects the wrong answer on purpose.
try:
got = profit("BUY", 100.0, 104.5, 2)
assert got == 10.0, "expected 10.0, got " + str(got)
except AssertionError as problem:
print("A failing assert says:", problem)Your progress
Your marks are kept in this browser only, in its local storage. They are not sent anywhere, there is no account, and they will not follow you to another browser or device. To remove them, use Clear my progress on the learning track page, or clear this site's data in your browser's settings. Marking needs JavaScript. With scripts off the track reads the same, without the ticks.
This browser refused to store anything for this page, so marks cannot be kept here.
Where this sits in the path
Stage 1 of 4: First steps. Values, decisions, loops and functions, and the habit of testing what you wrote. No files and no account.
- First Python scriptDone
- Variables and typesDone
- If and comparisonsDone
- Lists and loopsDone
- Functions and sizingDone
- Test your functionsDoneYou are here