- Tests affecting each other
- Conditional constructions in tests
- Tests outside of tests
- Tests with too much detail
- Code with tests versus without tests
Like any other code, you can write tests in many ways, including terrible ones. In addition to some general coding practices and standards, tests have their peculiarities that you need to know about. In this lesson, we'll go over some of them.
Tests affecting each other
One of the most important rules is that tests should not interfere with each other. It means each test should run as if no other test existed. It's easy to break this rule. One test may create a file, change a variable, or write something to the database.
If any of the other tests stumble upon these changes, they may not work:
- Fail where they shouldn't
- Pass where they shouldn't
In addition, this situation also means uncertainty. Such tests may crash for no apparent reason. For example, when the test runs in isolation, it works, but when the other tests run in parallel, it crashes:
user = None
def test_first():
user = { 'name': 'Vasya' }
# Here is the test logic
def test_second():
# We use a user created by another test
# This test depends on how the previous test works
# It cannot work without running both tests in sequence
user["name"] = 'Petya'
This situation is common in tests that actively interact with the external environment, such as a database or file system. Testing side effects has its tricks, which we'll look at in the advanced testing course.
Conditional constructions in tests
Let us observe the code:
def test_something():
if (something):
# Executing the code one way
# A check can be here
else:
# Executing the code in a different way
# A check can be here
# A check can be here
Any branching in tests is multiple tests in one. Branching generates code that we need to test itself. It happens because we no longer know the order of execution and can accidentally violate it. It's better to create independent tests for each branch.
Tests outside of tests
Fixtures prepare data and environment for testing. Test functions perform checks and invoke code for testing. But sometimes developers overdo it:
# Testing the `sum()` function
import pytest
@pytest.fixture
def result():
# We call the code under test
return sum([5, 9])
def test_sum(result):
assert result == 14
The tested code is called in the result()
function. This approach complicates test analysis because it turns everything upside down.
Tests with too much detail
Some programmers try to spread their code across files, modules, and functions as much as possible. The same can happen with tests. Instead of creating one test with five checks, they make five tests with one check:
def test_create_user():
user = { 'name': 'Mark', 'age': 28 }
# The code adds a user to the database
assert user['age'] == 28
def test_create_user():
user = { 'name': 'Mark', 'age': 28 }
# The code adds a user to the database
assert user['name'] == 'Mark'
This separation has one consequence: there's more code, which means it's harder to refactor in the future.
Code with tests versus without tests
This crucial question helps to understand how good a programmer is at writing tests.
Some types of tests are complicated and require extra time. But at the same time, everyday tests written along with the code should lead to faster development. There are good reasons for this:
- Tests help to identify unsuccessful solutions earlier and thus influence the design of the code
- Tests simplify the preparation of input data; you only need to prepare it once
- Tests help to verify your code because they check everything, including borderline cases
- Tests make refactoring easier and faster because you don't have to check parts of the code manually
- Tests improve the team atmosphere by reducing stress levels
Are there any more questions? Ask them in the Discussion section.
The Hexlet support team or other students will answer you.
For full access to the course you need a professional subscription.
A professional subscription will give you full access to all Hexlet courses, projects and lifetime access to the theory of lessons learned. You can cancel your subscription at any time.