COMP1531
🐶 Software Engineering
1.4 - Testing - Intro
Author: Hayden Smith 2021
In this lecture
Why?
- Writing tests are critical to ensure an application works
- Approaching testing the right way will yield better results
What?
- Abstraction & Black boxes
- Design by contract
- Pytest
Seeing if your code works
If I left you alone right now, how would you check if this function works correctly?
def get_even(nums):
evens = []
for number in nums:
if number % 2 == 0:
evens.append(number)
return evens
even_testing.py
Seeing if your code works
Would you do something like this?
def get_even(nums):
evens = []
for number in nums:
if number % 2 == 0:
evens.append(number)
return evens
print(get_event([1,2,3]))
print(get_event([4,5,6]))
print(get_event([7]))
even_testing.py
Seeing if your code works
Or something like this?
def get_even(nums):
evens = []
for number in nums:
if number % 2 == 0:
evens.append(number)
return evens
if get_event([1,2,3]) != [2]:
print("Doesn't work 1")
if get_event([4,5,6]) != [4,6]:
print("Doesn't work 2")
if get_event([7]) != []:
print("Doesn't work 3")
even_testing.py
Using python assert
Printing errors or visually inspecting output is a method of debugging not testing. You can't call something a testing method if it doesn't scale well.If I left you alone right now, how would you check if this function works correctly?
def get_evens(nums):
evens = []
for number in nums:
if number % 2 == 0:
evens.append(number)
return evens
assert(get_evens([1,2,3]) == [2])
assert(get_evens([4,5,6]) == [4,6])
assert(get_evens([7]) == [])
even_assert.py
Python has an assert function built-in that will cause an error if what it's provided is not true.
Blackbox Testing & Abstraction
Abstraction is the notion of focusing on a higher level understanding of the problem and not worrying about the underlying detail.
We do this all the time when we drive a car, use our computers, order something online. You're typically focused on expressing an input and wanting an output, with little regard for how you get that output.
When we look at systems in an abstract way we could also say that we're treating them like black boxes.
Blackbox Testing & Abstraction
When we're testing our code, we always want to view the functions we're testing as abstractions / black boxes.
Let's try and write some tests
# Returns a new string with vowels removed
def remove_vowels(string):
pass
# Calculates the factorial of a number
def factorial(num):
pass
blackbox.py
Blackbox Testing & Abstraction
What do we notice when writing these tests?
- The tests are complete, even if they aren't being passed
- We don't need to know how the function is implemented to test the function
- Now we can go and implement it, and we have tests already done!
# Returns a new string with vowels removed
def remove_vowels(string):
pass
# Calculates the factorial of a number
def factorial(num):
pass
assert(remove_vowels("abcde") == "bcd")
assert(remove_vowels("frog") == "frg")
assert(factorial(3) == 6)
assert(factorial(5) == 120)
blackbox_testing.py
Design by contract
When we're testing or implementing a function, we will typically be working with information that tells us the constraints placed on at least the inputs.
The documentation can come in a variety of forms.
This information tells us what we do and don't need to worry about when writing tests.
# Returns a new string with vowels removed
# Input is a non-empty string type
# Return type is another string
def remove_vowels(string):
pass
# Calculates the factorial of a number
# Input is a number between 1 and 10
# Output is a positive number
def factorial(num):
pass
blackbox_contract.py
Systematic Python Testing
Let's take a look at pytest
What is pytest?
- A structured method of writing, organising, and running tests
- pytest is a library that helps us write small tests, but can also be used to write larger and more complex tests
- pytest comes with a binary that we run on command line
- pytest detects any function prefixed with test and runs that function, processing the assertions inside
pytest - basic
def sum(x, y):
return x * y
def test_sum1():
assert sum(1, 2) == 3
test_sum1()
$ python3 test1_nopytest.py
test1_nopytest.py
def sum(x, y):
return x * y
def test_sum1():
assert sum(1, 2) == 3, "1 + 2 == 3"
$ pytest test1_pytest.py
test1_pytest.py
pytest - more complicated
import pytest
def sum(x, y):
return x + y
def test_small():
assert sum(1, 2) == 3, "1, 2 == "
assert sum(3, 5) == 8, "3, 5 == "
assert sum(4, 9) == 13, "4, 9 == "
def test_small_negative():
assert sum(-1, -2) == -3, "-1, -2 == "
assert sum(-3, -5) == -8, "-3, -5 == "
assert sum(-4, -9) == -13, "-4, -9 == "
def test_large():
assert sum(84*52, 99*76) == 84*52 + 99*76, "84*52, 99*76 == "
assert sum(23*98, 68*63) == 23*98 + 68*63, "23*98, 68*63 == "
A more complicated test
test_multiple.py
pytest - prefixes
If you just run
$ pytest
It will automatically look for any files in that directory in the shape:
- test_*.py
- *_test.py
And then any functions that are prefixed with test_ in those files will be run
pytest - particular files
You can run specific functions within your test files with the -k command. For example, we if want to run the following:
- test_small
- test_small_negative
test_large
We could run
$ pytest -k small
or try
$ pytest -k small -v
pytest - markers
We can also use a range of decorators to specify tests in python:
import pytest
def pointchange(point, change):
x, y = point
x += change
y += change
return (x, y)
@pytest.fixture
def supply_point():
return (1, 2)
@pytest.mark.up
def test_1(supply_point):
assert pointchange(supply_point, 1) == (2, 3)
@pytest.mark.up
def test_2(supply_point):
assert pointchange(supply_point, 5) == (6, 7)
@pytest.mark.up
def test_3(supply_point):
assert pointchange(supply_point, 100) == (101, 102)
@pytest.mark.down
def test_4(supply_point):
assert pointchange(supply_point, -5) == (-4, -3)
@pytest.mark.skip
def test_5(supply_point):
assert False == True, "This test is skipped"
@pytest.mark.xfail
def test_6(supply_point):
assert False == True, "This test's output is muted"
pytest - more
There are a number of tutorials online for pytest. This is a very straightforward one.
pytest - project structure
import mymath
def test_sum1():
assert mymath.sum(1, 2) == 3, "1 + 2 == 3"
$ pytest
mymath_test.py
def sum(x, y):
return x * y
mymath.py
Whilst importing is covered in week 2, it's worth mentioning at a high level now for the project.
For the major project, your tests and implementation will be separated in different files. So writing tests will consist of importing other files to use them.
Feedback
COMP1531 21T3 - 1.4 - Testing - Intro
By haydensmith
COMP1531 21T3 - 1.4 - Testing - Intro
- 1,136