Unit Testing
Testing
Why Test?
- Car crash tests
- Compliance tests
- Quality tests in industry
- Statistical tests
In Computer Science
- To verify that your program works
- To detect errors
- To avoid regressions (when you modify code)
No test is perfect, but it still helps eliminate many errors.
Definition
A test resembles a scientific experiment.
It examines a hypothesis expressed in terms of three elements:
- Input data
- The object to test
- Expected results
This examination is conducted
- under controlled conditions
- with the goal to draw conclusions
- and ideally, be reproducible.
Test Coverage
- Percentage of functions tested
- ONE quality indicator
- Trend, rather than a reliable value
Be lazy : fewer tests, but useful ones!
Types of Tests
There are many different types of tests, here are the main ones:
- Unit test
- Functional test
- Load test
- Integration test
- Penetration test
Unit Testing
We will use the pytest package to perform our tests in Python.
A Good Unit Test
- Tests a single functionality
- Isolated
- Reproducible
- Deterministic
Method to Test
operations_mathematiques.py
class MathOperations:
"""Mathematical Operations"""
def divide_five_by(self, nb) -> float:
"""Divides the number 5 by a given number.
Parameters
----------
nb : float or int
The number by which 5 will be divided.
Returns
-------
float or None
The result of dividing 5 by the given number.
If the number is equal to 0, the method returns None.
"""
if nb != 0:
return 5 / nb
else:
return NoneTest Class
Let’s create a test class.
To test the nominal case (=“normal” case), we:
- Choose an input number
- Call the
divide_five_by()method - Verify that the returned value is equal to the expected value
Nominal Case
test_operations_mathematiques.py
import pytest
from mathematiques.operations_mathematiques import MathOperations
class TestMathOperations():
def test_divide_five_by_non_null_nb(self):
# GIVEN
nombre = 2
# WHEN
resultat = MathOperations().divide_five_by(nombre)
# THEN
assert resultat == 2.5Other Cases
But this is not sufficient!
- The method also has another possible return:
None - We also need to test this case
test_operations_mathematiques.py
def test_divide_five_by_zero(self):
# GIVEN
nombre = 0
# WHEN
resultat = MathOperations().diviser_cinq_par(nombre)
# THEN
assert resultat is NoneWhat If…
We call the method with this parameter: divide_five_by("a")?
You can also write a test to verify that your method indeed returns a TypeError exception in this case.
test_operations_mathematiques.py
def test_divide_five_by_string(self):
# GIVEN
nombre = "a"
# WHEN / THEN
with pytest.raises(TypeError):
MathOperations().diviser_cinq_par(nombre)Key Takeaways
Unit tests:
- Verify that a method does what it is supposed to do
- Test nominal cases, but also edge cases and errors
- A unit test tests ONE and ONLY ONE thing
- As many unit tests as there are possible returns
Mock
- Simulated object that replaces a real component during tests
- Isolating external dependencies = test code independently
- Simulate complex scenarios like network errors
Mock - Example
player_service.py
class PlayerService:
def create(self, pseudo, mdp, age, mail, fan_pokemon) -> Player:
new_player = Player(
pseudo=pseudo,
mdp=hash_password(mdp, pseudo),
age=age,
mail=mail,
fan_pokemon=fan_pokemon,
)
create_ok = PlayerDao().creer(new_player)
if create_ok:
return new_player
else:
return NoneMock - Example
test_player_service.py
from unittest.mock import MagicMock
def test_create_ok():
"""Successful creation of Player"""
# GIVEN
pseudo, mdp, age, mail, fan_pokemon = "jp", "1234", 15, "z@mail.oo", True
PlayerDao().creer = MagicMock(return_value=True)
# WHEN
player = PlayerService().creer(pseudo, mdp, age, mail, fan_pokemon)
# THEN
assert isinstance(player, Player)
assert player.pseudo == pseudoTest-Driven Development (TDD)
When to Test?
At the beginning!
The earlier you test, the more effective and less costly the tests are!
Test-Driven Development
The best practice:
- Create the tests
- Code the function
Logic
It may seem a bit strange !?
But…
When you code a function, you know before you start:
- What the input parameters will be
- What results you expect as output
- So you already know what to test!
TDD Practice
- ✅ Improvement of code quality
- ✅ Reduction of bugs
- ❌ then ✅ Time
- ❌ Maintenance of tests
Advantages >>> Disadvantages
Project tip
Designate the Test Police (or the Quality Police)
Why ?
- Ensure all important methods are tested
- Maintain consistency in testing and documentation
- Promote the test-first thinking
Benefits on team spirit
- Contribute to code quality without coding A-Z
- One person has an idea of the whole architecture and the pain points
- Easier to review when it’s not your code
