Register to get access to free programming courses with interactive exercises

Bad and good testing practices Python: Automated testing

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.

About Hexlet learning process

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.

Get access
130
courses
1000
exercises
2000+
hours of theory
3200
tests

Sign up

Programming courses for beginners and experienced developers. Start training for free

  • 130 courses, 2000+ hours of theory
  • 1000 practical tasks in a browser
  • 360 000 students
By sending this form, you agree to our Personal Policy and Service Conditions

Our graduates work in companies:

<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.bookmate">Bookmate</span>
<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.healthsamurai">Healthsamurai</span>
<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.dualboot">Dualboot</span>
<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.abbyy">Abbyy</span>
Suggested learning programs
profession
new
Developing web applications with Django
10 months
from scratch
under development
Start at any time

Use Hexlet to the fullest extent!

  • Ask questions about the lesson
  • Test your knowledge in quizzes
  • Practice in your browser
  • Track your progress

Sign up or sign in

By sending this form, you agree to our Personal Policy and Service Conditions
Toto Image

Ask questions if you want to discuss a theory or an exercise. Hexlet Support Team and experienced community members can help find answers and solve a problem.