✦ Python Intermediate & OOP Level 2 final project

L2 · Concept 04 · Final Project: Task Manager

Test Your Task Manager
Before You Rely on It.

Your Task Manager can now manage and save tasks. Add focused automatic tests so later code changes do not quietly break that behavior.

Learning goalWrite focused unittest checks for TaskManager methods, expected ValueError cases, and JSON persistence without changing your real project data.
01 Learn02 Work locally03 Check your understanding

Learning as a guest

Learning as a guest? Save your progress!

Create a free account to learn with Sloby AI, check your understanding, and track your progress.

The entire platform is free throughout 2026. Foundations stays free forever.

Manual checks are useful. Automatic checks remember.

You already ran TaskManager methods by hand in main.py. That is useful while building, but each later change can require repeating the same checks. An automatic test records one expectation and lets Python check it again whenever you run the tests.

import unittest


class TestTaskManager(unittest.TestCase):
    def setUp(self):
        self.manager = TaskManager()
        self.task = Task("task-1", "Read the plan")
        self.manager.add_task(self.task)

    def test_complete_task_marks_task_complete(self):
        self.manager.complete_task("task-1")
        self.assertTrue(self.task.completed)

unittest is part of Python’s standard library, so no installation is needed. A class derived from unittest.TestCase groups related tests. Each method whose name starts with test_ is discovered and run.

python3 -m unittest -v # or: python -m unittest -v

setUp() creates a fresh manager and task before every test method. This keeps tests independent: one test cannot accidentally leave changed state for the next one.

↳

One expectation at a time: assertEqual() compares an actual value with the expected value. assertTrue() checks that a condition is True. A failing assertion is useful information: first decide whether the code or the expectation is wrong.

Test an expected error
with self.assertRaises(ValueError):
    self.manager.get_task("missing-task")

assertRaises() passes only when the expected ValueError happens. Use it for a missing task_id after first deciding that ValueError is the project rule.

Test behavior, not implementation details
self.assertTrue(self.task.completed)

A test for complete_task() can check that completed becomes True. It does not need to care how many internal lines the method uses.

Run tests from the project folder
python -m unittest

Put tests in a clearly named test file and run python -m unittest from the Task Manager project folder. The test runner finds methods that begin with test_.

Keep JSON tests separate from real data
manager.save_tasks(test_path)

Later, pass a small temporary path to save_tasks(path) and load_tasks(path) for JSON checks. A test should never overwrite the data/tasks.json file you use while learning.

A small testing routine

  1. Arrange known dataCreate the TaskManager and Task values the test needs, usually in setUp().
  2. Act onceCall one TaskManager method, such as complete_task() or get_task().
  3. Assert the resultUse assertEqual(), assertTrue(), or assertRaises() to compare the actual behavior with one clear expectation.

Common Task Manager testing mistakes

  • Tests depend on an earlier testDo not assume another test already added a task. Give every test its own known starting state with setUp().
  • Changing real learning dataDo not point a test at the JSON file you use in your normal project. Pass a separate path to save_tasks(path) and load_tasks(path) when you create isolated temporary test data.
  • Expecting an error without checking the ruleUse assertRaises(ValueError) only when the project deliberately treats that input as invalid, such as a missing task_id.

Test Your Finished Task Manager

Do this in VS Code

Continue in the extracted task_manager_starter folder from Concepts 28–30. The lesson diagrams call the project task_manager, but you should keep working in the same starter folder. Create focused tests for the behavior you already built; do not start a new project or download a new ZIP.

Your existing project
task_manager/
├── main.py
├── models/
│   └── task.py
├── services/
│   └── task_manager.py
├── data/
│   └── tasks.json
└── tests/
    ├── __init__.py
    └── test_task_manager.py

No new ZIP is needed. Keep your working Task Manager and add a tests package beside the application code, so test files stay separate from main.py and the services.

Build your tests step by step

  1. Create the tests packageIn the project root, create tests/, an empty tests/__init__.py file, and tests/test_task_manager.py. In the test file, import Task from models.task and TaskManager from services.task_manager.
    from models.task import Task
    from services.task_manager import TaskManager
    import unittest
  2. Write one add_task() testUse unittest.TestCase and setUp() to make a fresh TaskManager and Task for every test. Arrange known objects, call add_task(), then assert that the manager has one task and that it is the Task you added.
    class TestTaskManager(unittest.TestCase):
        def setUp(self):
            self.manager = TaskManager()
            self.task = Task("task-1", "Read the plan")
  3. Test the operations you already builtAdd focused tests for get_task(), complete_task(), and delete_task(). Start with one behavior per test: find the same Task, mark completed as True, or remove the matching Task from the list.
    self.manager.complete_task("task-1")
    self.assertTrue(self.task.completed)
  4. Test expected ValueError casesUse assertRaises(ValueError) for the project rules from Concept 29: a duplicate task_id is rejected, and looking up or completing a missing task_id raises ValueError.
    with self.assertRaises(ValueError):
        self.manager.get_task("missing-task")
  5. Use a temporary JSON pathTemporaryDirectory creates an empty folder that Python removes when the with block ends. Build a test_path inside it, then pass that path to save_tasks(path) and load_tasks(path). This keeps your real data/tasks.json file safe.
    from pathlib import Path
    from tempfile import TemporaryDirectory
    
    with TemporaryDirectory() as temp_dir:
        test_path = Path(temp_dir) / "tasks.json"
  6. Check save, load, and replacementSave tasks to test_path, then load them into a new TaskManager. Check that the loaded items are Task objects. Add one temporary in-memory task before loading and verify that a successful load replaces that list rather than appending to it.
    writer.save_tasks(test_path)
    reader.load_tasks(test_path)
  7. Run the whole test suiteFrom the project root, discover every test in tests/. Read the first FAIL or ERROR carefully, then inspect the named test and its expected behavior before changing code.
    python -m unittest discover -s tests
    # or: python3 -m unittest discover -s tests
Arrange → Act → Assert in your project
# Arrange
self.manager.add_task(self.task)
# Act
found = self.manager.get_task("task-1")
# Assert
self.assertEqual(found, self.task)

Keep each test small. This example checks only that get_task() returns the Task with the requested ID; write separate tests for completion and deletion.

Typical terminal result
.......
----------------------------------------------------------------------
Ran 7 tests in 0.012s

OK

OK means every test finished and every assertion passed. FAIL means an assertion did not match its expectation. ERROR means an unexpected problem stopped a test before its assertion could finish.

↳

Final project checklist: add → find → complete → delete → expected ValueError → save → load → test suite Confirm that basic operations work, expected exceptions are reported, JSON save and load work with a separate test path, tests begin independently, and the complete suite reports OK.

Before asking Sloby for help

  • Fix imports before changing testsRun the command from the project root. Check that tests/__init__.py exists and that imports use models.task and services.task_manager exactly.
  • Name files and methods for discoveryThe file should begin with test_ and each test method should begin with test_. Otherwise unittest may not run it.
  • Read FAIL and ERROR differentlyFAIL means an assertion disagreed with the result. ERROR means the test could not finish, often because of an import, typo, or unexpected exception.
  • Remove hidden test-order dependenciesIf a test passes alone but fails in the full suite, make sure setUp() creates all tasks and managers that test needs.
  • Protect your real JSON filePass a temporary test_path to save_tasks(path) and load_tasks(path). Never point a test at your normal data/tasks.json file.
  • Check the expected valueBefore changing assertEqual(), decide whether the implementation or the expected result is wrong. A test should describe the project rule, not hide a bug.
↳

You have now combined classes, modules, JSON files, exceptions, and automatic tests in one local project. Finishing the checklist does not change your learning progress automatically; use it as evidence of what you can explain and verify.

What happens if this test expects ValueError but get_task() returns normally?

Read the assertion as a promise: the code inside the with block must raise ValueError.

with self.assertRaises(ValueError):
    manager.get_task("missing-task")
Choose the output

Build confidence independently

Keep tasks safe when the JSON file is missing

Write one focused test for the missing-file rule instead of changing your normal project data.

Your goalCreate a TaskManager with one known Task. Call load_tasks(test_path) where test_path is a path inside a temporary folder but the file does not exist. Verify that the original Task is still present and that the test does not create or change data/tasks.json.
task_manager/
├── services/task_manager.py
└── tests/test_task_manager.py

Work in your local VS Code folder

Progressive hints
  • Start with setUp() so the manager already contains one Task before the load attempt.
  • Create test_path inside TemporaryDirectory(), but do not create the JSON file itself.
  • After load_tasks(test_path), compare the task count and the known task_id with their values before the call.

Ask Sloby for one guiding question about independent test data or the missing-file rule. Do not ask it to write the finished test for you.

Sloby, the Python AI Tutor mascot

Your learning mentor

Sign in to ask Sloby

Create a free account or sign in to get AI guidance while you learn.

Sign in to check your understanding

Create a free account or sign in to receive AI feedback and save your learning progress.

Python AI Tutor · Learn by understanding, not just by running code.