Testing and Debugging

The tests directory demonstrates how unit testing is used to ensure the code’s functionality and correctness.

Unit Testing

Unit tests are designed to verify the behavior of individual functions or components. They provide a framework for ensuring that each part of the codebase works as expected. The tests/test_exporter.py file contains several unit tests for the BalenaCollector class.

Example:

    @mock.patch("main.BalenaCollector.get_fleet_metrics")
              def test_collect(self, mock_metrics):
                  with mock.patch.object(self.collector, "get_balena_fleets") as mock_fleets:
                      mock_fleets.return_value = ["fleet1", "fleet2"]
                      mock_metrics.side_effect = [("test_fleet", 3), ("test_fleet", 3)]
          
                      result = list(self.collector.collect()[0].samples)
                      expected = [('balena_devices_online', {'fleet_name': 'test_fleet'}, 3),
                                  ('balena_devices_online', {'fleet_name': 'test_fleet'}, 3)]
          
                      result = [(s.name, s.labels, s.value) for s in result]
          
                      self.assertEqual(result, expected)
          

Source: tests/test_exporter.py

Explanation:

The test uses mock to simulate the behavior of the get_balena_fleets and get_fleet_metrics methods, setting their return values. It then calls the collect method, compares the actual result with the expected output, and uses self.assertEqual to verify the expected behavior.

Mocking and Patching

The mock library is used to create mock objects that mimic the behavior of real objects. Patching allows replacing functions or methods with mock objects, enabling the isolation of code under test.

Example:

    with mock.patch("main.requests.get") as mock_get:
                  mock_get.return_value.ok = True
                  mock_get.return_value.json.return_value = {"d": [{"id": "fleet1"}, {"id": "fleet2"}]}
                  result = list(self.collector.get_balena_fleets())
                  expected = ["fleet1", "fleet2"]
                  self.assertEqual(result, expected)
          

Source: tests/test_exporter.py

Explanation:

This test patches the requests.get function, simulating a successful API call and setting the response content.

Debugging

Debugging is the process of identifying and resolving errors within the codebase.

Common Debugging Tools:

  • Print statements: Simple but effective for tracing code execution and variable values.

  • Debugging tools: Integrated debuggers (such as pdb in Python) provide tools for stepping through code, inspecting variables, and setting breakpoints.

  • Logging: Logging statements can be used to record events and debug information, making it easier to analyze issues over time.

Running Tests

To run the unit tests, use the following command:

python -m unittest tests/test_exporter.py
          

Source: tests/test_exporter.py

Explanation:

The unittest module is used to execute the unit tests.

Project Setup

Dependencies:

  • Python

  • Requirements: The requirements.txt file lists all necessary dependencies.

certifi==2024.7.4
          charset-normalizer==3.1.0
          idna==3.4
          prometheus-client==0.16.0
          requests==2.31.0
          urllib3==1.26.18
          

Source: requirements.txt

Running the Application:

  1. Build the Docker image.
docker build -t balena-exporter .
          
  1. Run the Docker container.
docker run -d \
          --name balena-exporter \
          -p 8000:8000 \
          -e BALENA_TOKEN= \
          balena-exporter
          

Source: README.md

Explanation:

The Docker image is built using the Dockerfile in the repository. The container is run with the BALENA_TOKEN environment variable set to your Balena API token.

Top-Level Directory Explanations

tests/ - This directory contains all the unit and integration tests for the project. It includes the __init__.py file which makes it a package, and specific test files like test_exporter.py.