Back to Repositories

Testing CoverAgent Core Functionality in Codium-ai/cover-agent

This test suite validates the core functionality of the CoverAgent class, which handles test coverage analysis and generation in the Codium-ai/cover-agent project. It comprehensively tests argument parsing, file handling, and coverage reporting mechanisms.

Test Coverage Overview

The test suite provides extensive coverage of the CoverAgent class functionality:
  • Command-line argument parsing and validation
  • File path verification and error handling
  • Test file duplication and output management
  • Coverage report generation and validation
  • Maximum iteration handling with strict coverage requirements
  • Differential coverage analysis

Implementation Analysis

The testing approach employs pytest fixtures and unittest.mock for isolation:
  • Extensive use of @patch decorators for dependency mocking
  • Temporary file handling for file operation tests
  • Mock objects to simulate file system and coverage reporting
  • Assertion-based validation of expected behaviors

Technical Details

Testing infrastructure includes:
  • pytest framework for test execution
  • unittest.mock for dependency isolation
  • tempfile for temporary file management
  • argparse for command-line argument testing
  • Custom assertion patterns for error validation

Best Practices Demonstrated

The test suite exemplifies several testing best practices:
  • Comprehensive error case coverage
  • Isolated test cases with proper teardown
  • Meaningful assertion messages
  • Proper resource cleanup
  • Structured test organization
  • Mock usage for external dependencies

codium-ai/cover-agent

tests/test_CoverAgent.py

            
from cover_agent.CoverAgent import CoverAgent
from cover_agent.main import parse_args
from unittest.mock import patch, MagicMock
import argparse
import os
import pytest
import tempfile

import unittest
class TestCoverAgent:
    def test_parse_args(self):
        with patch(
            "sys.argv",
            [
                "program.py",
                "--source-file-path",
                "test_source.py",
                "--test-file-path",
                "test_file.py",
                "--code-coverage-report-path",
                "coverage_report.xml",
                "--test-command",
                "pytest",
                "--max-iterations",
                "10",
            ],
        ):
            args = parse_args()
            assert args.source_file_path == "test_source.py"
            assert args.test_file_path == "test_file.py"
            assert args.project_root == ""
            assert args.code_coverage_report_path == "coverage_report.xml"
            assert args.test_command == "pytest"
            assert args.test_command_dir == os.getcwd()
            assert args.included_files is None
            assert args.coverage_type == "cobertura"
            assert args.report_filepath == "test_results.html"
            assert args.desired_coverage == 90
            assert args.max_iterations == 10

    @patch("cover_agent.CoverAgent.UnitTestGenerator")
    @patch("cover_agent.CoverAgent.ReportGenerator")
    @patch("cover_agent.CoverAgent.os.path.isfile")
    def test_agent_source_file_not_found(
        self, mock_isfile, mock_report_generator, mock_unit_cover_agent
    ):
        args = argparse.Namespace(
            source_file_path="test_source.py",
            test_file_path="test_file.py",
            project_root="",
            code_coverage_report_path="coverage_report.xml",
            test_command="pytest",
            test_command_dir=os.getcwd(),
            included_files=None,
            coverage_type="cobertura",
            report_filepath="test_results.html",
            desired_coverage=90,
            max_iterations=10,
        )
        parse_args = lambda: args
        mock_isfile.return_value = False

        with patch("cover_agent.main.parse_args", parse_args):
            with pytest.raises(FileNotFoundError) as exc_info:
                agent = CoverAgent(args)

        assert (
            str(exc_info.value) == f"Source file not found at {args.source_file_path}"
        )

        mock_unit_cover_agent.assert_not_called()
        mock_report_generator.generate_report.assert_not_called()

    @patch("cover_agent.CoverAgent.os.path.exists")
    @patch("cover_agent.CoverAgent.os.path.isfile")
    @patch("cover_agent.CoverAgent.UnitTestGenerator")
    def test_agent_test_file_not_found(
        self, mock_unit_cover_agent, mock_isfile, mock_exists
    ):
        args = argparse.Namespace(
            source_file_path="test_source.py",
            test_file_path="test_file.py",
            project_root="",
            code_coverage_report_path="coverage_report.xml",
            test_command="pytest",
            test_command_dir=os.getcwd(),
            included_files=None,
            coverage_type="cobertura",
            report_filepath="test_results.html",
            desired_coverage=90,
            max_iterations=10,
            prompt_only=False,
        )
        parse_args = lambda: args
        mock_isfile.side_effect = [True, False]
        mock_exists.return_value = True

        with patch("cover_agent.main.parse_args", parse_args):
            with pytest.raises(FileNotFoundError) as exc_info:
                agent = CoverAgent(args)

        assert str(exc_info.value) == f"Test file not found at {args.test_file_path}"

    @patch("cover_agent.CoverAgent.shutil.copy")
    @patch("cover_agent.CoverAgent.os.path.isfile", return_value=True)
    def test_duplicate_test_file_with_output_path(self, mock_isfile, mock_copy):
        with tempfile.NamedTemporaryFile(suffix=".py", delete=False) as temp_source_file:
            with tempfile.NamedTemporaryFile(suffix=".py", delete=False) as temp_test_file:
                args = argparse.Namespace(
                    source_file_path=temp_source_file.name,
                    test_file_path=temp_test_file.name,
                    project_root="",
                    test_file_output_path="output_test_file.py",  # This will be the path where output is copied
                    code_coverage_report_path="coverage_report.xml",
                    test_command="echo hello",
                    test_command_dir=os.getcwd(),
                    included_files=None,
                    coverage_type="cobertura",
                    report_filepath="test_results.html",
                    desired_coverage=90,
                    max_iterations=10,
                    additional_instructions="",
                    model="openai/test-model",
                    api_base="openai/test-api",
                    use_report_coverage_feature_flag=False,
                    log_db_path="",
                    diff_coverage=False,
                    branch="main",
                    run_tests_multiple_times=1,
                )

                with pytest.raises(AssertionError) as exc_info:
                    agent = CoverAgent(args)
                    failed_test_runs = agent.test_validator.get_coverage()
                    agent.test_gen.build_prompt(failed_test_runs)
                    agent._duplicate_test_file()

                assert "Fatal: Coverage report" in str(exc_info.value)
                mock_copy.assert_called_once_with(args.test_file_path, args.test_file_output_path)

        # Clean up the temp files
        os.remove(temp_source_file.name)
        os.remove(temp_test_file.name)

    @patch("cover_agent.CoverAgent.os.path.isfile", return_value=True)
    def test_duplicate_test_file_without_output_path(self, mock_isfile):
        with tempfile.NamedTemporaryFile(suffix=".py", delete=False) as temp_source_file:
            with tempfile.NamedTemporaryFile(suffix=".py", delete=False) as temp_test_file:
                args = argparse.Namespace(
                    source_file_path=temp_source_file.name,
                    test_file_path=temp_test_file.name,
                    project_root="",
                    test_file_output_path="",  # No output path provided
                    code_coverage_report_path="coverage_report.xml",
                    test_command="echo hello",
                    test_command_dir=os.getcwd(),
                    included_files=None,
                    coverage_type="cobertura",
                    report_filepath="test_results.html",
                    desired_coverage=90,
                    max_iterations=10,
                    additional_instructions="",
                    model="openai/test-model",
                    api_base="openai/test-api",
                    use_report_coverage_feature_flag=False,
                    log_db_path="",
                    diff_coverage=False,
                    branch="main",
                    run_tests_multiple_times=1,
                )

                with pytest.raises(AssertionError) as exc_info:
                    agent = CoverAgent(args)
                    failed_test_runs = agent.test_validator.get_coverage()
                    agent.test_gen.build_prompt(failed_test_runs)
                    agent._duplicate_test_file()

                assert "Fatal: Coverage report" in str(exc_info.value)
                assert args.test_file_output_path == args.test_file_path

        # Clean up the temp files
        os.remove(temp_source_file.name)
        os.remove(temp_test_file.name)

    @patch("cover_agent.CoverAgent.os.environ", {})
    @patch("cover_agent.CoverAgent.sys.exit")
    @patch("cover_agent.CoverAgent.UnitTestGenerator")
    @patch("cover_agent.CoverAgent.UnitTestValidator")
    @patch("cover_agent.CoverAgent.UnitTestDB")
    def test_run_max_iterations_strict_coverage(self, mock_test_db, mock_unit_test_validator, mock_unit_test_generator, mock_sys_exit):
        with tempfile.NamedTemporaryFile(suffix=".py", delete=False) as temp_source_file:
            with tempfile.NamedTemporaryFile(suffix=".py", delete=False) as temp_test_file:
                args = argparse.Namespace(
                    source_file_path=temp_source_file.name,
                    test_file_path=temp_test_file.name,
                    project_root="",
                    test_file_output_path="output_test_file.py",
                    code_coverage_report_path="coverage_report.xml",
                    test_command="pytest",
                    test_command_dir=os.getcwd(),
                    included_files=None,
                    coverage_type="cobertura",
                    report_filepath="test_results.html",
                    desired_coverage=90,
                    max_iterations=1,
                    additional_instructions="",
                    model="openai/test-model",
                    api_base="openai/test-api",
                    use_report_coverage_feature_flag=False,
                    log_db_path="",
                    run_tests_multiple_times=False,
                    strict_coverage=True,
                    diff_coverage=False,
                    branch="main"
                )
                # Mock the methods used in run
                validator = mock_unit_test_validator.return_value
                validator.current_coverage = 0.5  # below desired coverage
                validator.desired_coverage = 90
                validator.get_coverage.return_value = [{}, "python", "pytest", ""]
                generator = mock_unit_test_generator.return_value
                generator.generate_tests.return_value = {"new_tests": [{}]}
                agent = CoverAgent(args)
                agent.run()
                # Assertions to ensure sys.exit was called
                mock_sys_exit.assert_called_once_with(2)
                mock_test_db.return_value.dump_to_report.assert_called_once_with(args.report_filepath)

    @patch("cover_agent.CoverAgent.os.path.isfile", return_value=True)
    @patch("cover_agent.CoverAgent.os.path.isdir", return_value=False)
    def test_project_root_not_found(self, mock_isdir, mock_isfile):
        args = argparse.Namespace(
            source_file_path="test_source.py",
            test_file_path="test_file.py",
            project_root="/nonexistent/path",
            test_file_output_path="",
            code_coverage_report_path="coverage_report.xml",
            test_command="pytest",
            test_command_dir=os.getcwd(),
            included_files=None,
            coverage_type="cobertura",
            report_filepath="test_results.html",
            desired_coverage=90,
            max_iterations=10
        )
        
        with pytest.raises(FileNotFoundError) as exc_info:
            agent = CoverAgent(args)
            
        assert str(exc_info.value) == f"Project root not found at {args.project_root}"

    @patch("cover_agent.CoverAgent.UnitTestValidator")
    @patch("cover_agent.CoverAgent.UnitTestGenerator")
    @patch("cover_agent.CoverAgent.UnitTestDB")
    @patch("cover_agent.CoverAgent.CustomLogger")
    def test_run_diff_coverage(self, mock_logger, mock_test_db, mock_test_gen, mock_test_validator):
        with tempfile.NamedTemporaryFile(suffix=".py", delete=False) as temp_source_file:
            with tempfile.NamedTemporaryFile(suffix=".py", delete=False) as temp_test_file:
                args = argparse.Namespace(
                    source_file_path=temp_source_file.name,
                    test_file_path=temp_test_file.name,
                    project_root="",
                    test_file_output_path="output_test_file.py",
                    code_coverage_report_path="coverage_report.xml",
                    test_command="pytest",
                    test_command_dir=os.getcwd(),
                    included_files=None,
                    coverage_type="cobertura",
                    report_filepath="test_results.html",
                    desired_coverage=90,
                    max_iterations=1,
                    additional_instructions="",
                    model="openai/test-model",
                    api_base="openai/test-api",
                    use_report_coverage_feature_flag=False,
                    log_db_path="",
                    run_tests_multiple_times=False,
                    strict_coverage=False,
                    diff_coverage=True,
                    branch="main"
                )
                mock_test_validator.return_value.current_coverage = 0.5
                mock_test_validator.return_value.desired_coverage = 90
                mock_test_validator.return_value.get_coverage.return_value = [{}, "python", "pytest", ""]
                mock_test_gen.return_value.generate_tests.return_value = {"new_tests": [{}]}
                agent = CoverAgent(args)
                agent.run()
                mock_logger.get_logger.return_value.info.assert_any_call(
                    f"Current Diff Coverage: {round(mock_test_validator.return_value.current_coverage * 100, 2)}%"
                )

        # Clean up the temp files
        os.remove(temp_source_file.name)
        os.remove(temp_test_file.name)