DEV Community

Cover image for Navigating the CI Seas: A Tale of Mocking Files and Testing Stranger Code
Tasbi Tasbi
Tasbi Tasbi

Posted on

Navigating the CI Seas: A Tale of Mocking Files and Testing Stranger Code

Ahoy, fellow developers! 🌊 Ever tried to sail the uncharted waters of someone else's codebase? Well, grab your compasses and let me regale you with my latest adventure in setting up Continuous Integration (CI) and writing tests for a project that wasn't mine. Spoiler alert: it involved a lot of mocking—both the technical kind and, well, the other kind too!


Setting Sail with GitHub Actions CI Workflow

First things first, I needed to set up a CI workflow for my own repository. I wanted to automate the testing process so that every push would run my tests and ensure everything was shipshape.

Here's what my .github/workflows/ci.yml looked like:

name: CI

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build-and-test:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3
    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: '3.x'
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements.txt
    - name: Run tests
      run: |
        python -m unittest discover
Enter fullscreen mode Exit fullscreen mode

Pretty standard stuff, right? This workflow checks out the code, sets up Python, installs dependencies, and runs the tests. Simple, yet effective—like a trusty compass.


Boarding the Ship: Writing Tests for a Stranger's Repo

Now, here's where the real adventure began. I was tasked with writing tests for HTSagara's readme_genie repository. Imagine being handed a treasure map in a language you only partially understand. Challenge accepted!


The Quest for Meaningful Tests

I decided to focus on the read_file_content function, which, as the name suggests, reads file content. I thought, "What could possibly go wrong with reading files?" Turns out, plenty—if you're testing!

I wrote tests to handle various scenarios:

  • Empty Files
  • Files with Only Comments
  • Large Files

Here's a peek at one of the tests:

@patch("builtins.open", new_callable=mock_open, read_data="")
def test_read_file_content_empty_file(self, mock_file):
    """Test read_file_content with an empty file."""
    file_content = read_file_content(["empty_file.py"])
    self.assertEqual(file_content, "\n\n")
Enter fullscreen mode Exit fullscreen mode

Mocking Files Like a Pro

Since I didn't want to actually create files, I used unittest.mock to simulate file operations. This was my first time heavily relying on mocking, and it felt like I was crafting illusions—like a code wizard!

Writing tests for someone else's codebase was like deciphering a coded message. I had to understand the function's intent, its edge cases, and how it handled unexpected input. It was both challenging and exhilarating.


Comparing Ships: My Repo vs. My Partner's

In my own repo, I had a cozy setup with straightforward tests. In contrast, HTSagara's repo was a grand vessel with intricate details. Their testing setup was minimal, so I had the freedom (and responsibility) to expand it.

The main differences were:

  • Test Coverage: My repo had tests covering most functions, while theirs had room for more.
  • Complexity: Their functions handled more edge cases, requiring more thoughtful tests.
  • CI Integration: Both of us used GitHub Actions, but the workflows had slight variations.

The Treasure of Continuous Integration

After setting up CI, I must say—I'm hooked! Having automated tests run on every push is like having a vigilant lookout who never sleeps. It catches issues early, ensures code quality, and gives peace of mind.

CI has transformed from a mysterious acronym to an indispensable tool in my development arsenal. It's like upgrading from a rowboat to a schooner.


Optional Challenges: Tackling the Behemoth

One of the optional challenges was to test the function's performance with a large file. I simulated a file with 1000 lines to see how the function held up:

@patch("builtins.open", new_callable=mock_open, read_data="line\n" * 10**6)
def test_read_file_content_large_file(self, mock_file):
    """Test read_file_content with a large file."""
    start_time = time.time()
    file_content = read_file_content(["large_file.py"])
    end_time = time.time()
    elapsed_time = end_time - start_time

    expected_content = ("line\n" * 10**6) + "\n\n"
    self.assertEqual(file_content, expected_content)
    self.assertLess(elapsed_time, 2, "Processing large files took too long!")
Enter fullscreen mode Exit fullscreen mode

This test not only checked if the function could handle large files but also if it did so efficiently. I'm happy to report that it passed with flying colors!


Lessons Learned and Final Thoughts

  • Writing Tests for Others: It's a fantastic way to deepen your understanding of testing and code structure.
  • Mocking is Powerful: unittest.mock is now a close friend. It opens up possibilities for testing without relying on actual I/O operations.
  • CI is Essential: Automating tests saves time and reduces errors. It's like having a trusty first mate.

Overall, this lab was a thrilling voyage through the seas of CI and testing. I encourage everyone to venture beyond their comfort zones—who knows what treasures you'll find?

Fair winds and following seas! 🌬️🚢

Feel free to share your own adventures or ask questions in the comments below. Happy coding!

Top comments (0)