Automate Debugging in Git with Unit Tests

Share this article

A while ago, I published an article on debugging a codebase in Git using two commands blame and bisect. Git blame involved checking the author of each line of a file, whereas bisect involves traversing through the commits (using binary search) to find the one that introduced the bug. In this post, we will see how to automate the process of bisect.

To refresh your memory, git bisect involved a few steps, which are summarized below:

  • Start the bisect wizard with git bisect start
  • Select “good” and “bad” commits, or known commits where the bug was absent and present, respectively
  • Assign commits to be tested as “good” or “bad” until Git finds out the commit which introduced the bug
  • Exit the wizard with git bisect reset

To get an idea of the whole process, you could have a look at this screencast, which shows in detail how the debugging process works.

Naturally, the third step was time consuming — Git would show you commits one by one and you had to label them as “good” or “bad” after checking if the bug was present in that commit.

When we write a script to automate the process of debugging, we’ll basically be running the third step. Let’s get started!

Staging the environment

In this post, I will write a small module in Python that contains a function which adds two numbers. This is a very simple task and I’m going to do this for demonstration purposes only. The code is self explanatory, so I won’t go into details.

#add_two_numbers.py
def add_two_numbers(a, b):
    '''
        Function to add two numbers
    '''
    addition = a + b
    return addition

To automate the process of Git Bisect, you need to write tests for your code. In Python, we’ll use the unittest module to write our test cases. Here’s what a basic test looks like.

#tests.py
import unittest
from add_two_numbers import add_two_numbers

class TestsForAddFunction(unittest.TestCase):

    def test_zeros(self):
        result = add_two_numbers(0, 0)
        self.assertEqual(0, result)

if __name__ == '__main__':
    unittest.main()

We could write more of these tests, but this was just to demonstrate how to get on with it. In fact, you should definitely write more test cases as your programs and apps are going to be far more complex than this.

To run the unit tests, execute the tests.py file containing your test cases.

python tests.py

If the tests pass, you should get the following output.

Tests success

Let’s now introduce an error in our function and commit the code.

def add_two_numbers(a, b):
    '''
        Function to add two numbers
    '''
    addition = a + 0
    return addition

To verify that the tests fail, let us run them again.

Test failures

Let us add a few more commits so that the commit that introduced the error is not the last.

Git history

Start the bisect process

For the git bisect wizard, we will select the latest commit as bad (b60fe2cf35) and the first one as good (98d9df03b6).

git bisect start b60fe2cf35 98d9df03b6

At this point, Git points us to a commit and asks us whether it’s a good or a bad commit. This is when we tell Git to run the tests for us. The command for it is as follows.

git bisect run [command to run tests]

In our case, it will turn out to be the following.

git bisect run python tests.py

When we provide Git the command to run the tests itself, rather than asking us, Git runs these tests for every revision and decides whether the commit should be assigned good or bad.

Git running unit tests

Once Git is done running tests for every commit, it figures out which commit introduced the error, like magic!

git bisect result

Once you have found your commit, don’t forget to reset the wizard with git bisect reset.

In place of your unit tests, you can also create a custom shell script with custom exit codes. In general an exit code of 0 is considered a success, everything else is a failure.

Final thoughts

As the size of your code base increases, writing unit tests for every little piece of code that you write becomes necessary. Writing tests may seem time-consuming, but as you’ve seen in this case, they help you in debugging and save you time in the long run.

How does your team debug errors in code? Let us know in the comments below.

Frequently Asked Questions (FAQs) about Automating Debugging with Git Unit Tests

How can I set up automated debugging with Git unit tests?

Setting up automated debugging with Git unit tests involves several steps. First, you need to create a Git repository and initialize it. Then, you need to write your unit tests using a testing framework compatible with your programming language. Once your tests are written, you can use a continuous integration (CI) tool to automate the running of these tests. This tool can be configured to run your tests every time you push changes to your Git repository. This way, you can catch and fix bugs early in the development process.

What are the benefits of automating debugging with Git unit tests?

Automating debugging with Git unit tests has several benefits. It helps to catch bugs early in the development process, which can save time and resources. It also ensures that all parts of your code are tested consistently. This can improve the overall quality of your code and make it more reliable. Additionally, it can make your development process more efficient by reducing the amount of manual testing you need to do.

What is continuous integration (CI) and how does it relate to Git unit tests?

Continuous integration (CI) is a development practice where developers integrate code into a shared repository frequently, usually multiple times per day. Each integration is then verified by an automated build and automated tests. In the context of Git unit tests, CI can be used to automate the running of these tests every time changes are pushed to the Git repository. This helps to catch bugs early and ensures that all parts of the code are tested consistently.

How can I write effective unit tests for my Git repository?

Writing effective unit tests involves several best practices. First, each test should focus on a single functionality or behavior. This makes it easier to identify the cause of any failures. Second, tests should be independent and able to run in any order. This ensures that the outcome of one test does not affect the outcome of another. Third, tests should be repeatable and yield the same results every time they are run. This ensures that your tests are reliable and can be trusted to catch bugs.

What tools can I use to automate debugging with Git unit tests?

There are several tools you can use to automate debugging with Git unit tests. These include continuous integration (CI) tools like Jenkins, Travis CI, and CircleCI. These tools can be configured to run your unit tests every time you push changes to your Git repository. Additionally, you can use testing frameworks like JUnit (for Java), pytest (for Python), and Mocha (for JavaScript) to write your unit tests.

How can I integrate my Git unit tests with a continuous integration (CI) tool?

Integrating your Git unit tests with a continuous integration (CI) tool involves several steps. First, you need to configure your CI tool to connect to your Git repository. Then, you need to configure it to run your unit tests every time changes are pushed to the repository. This usually involves writing a configuration file that specifies the commands to run the tests and the conditions under which to run them.

What should I do if my Git unit tests fail?

If your Git unit tests fail, the first step is to identify the cause of the failure. This usually involves examining the test output and the code that was being tested. Once you’ve identified the cause, you can make the necessary changes to your code and rerun the tests. If the tests pass, you can push your changes to the Git repository. If they fail again, you may need to revise your tests or your code until they pass.

Can I automate debugging with Git unit tests for any programming language?

Yes, you can automate debugging with Git unit tests for any programming language. However, the specific tools and techniques you use may vary depending on the language. Most programming languages have one or more testing frameworks that you can use to write your unit tests. Additionally, most continuous integration (CI) tools support multiple languages and can be configured to run your tests regardless of the language they are written in.

How can I ensure that my Git unit tests are effective?

Ensuring that your Git unit tests are effective involves several best practices. First, your tests should cover all parts of your code, including edge cases. This ensures that your tests are comprehensive. Second, your tests should be independent and able to run in any order. This ensures that the outcome of one test does not affect the outcome of another. Third, your tests should be repeatable and yield the same results every time they are run. This ensures that your tests are reliable.

Can I use Git unit tests to test the user interface (UI) of my application?

Git unit tests are typically used to test the functionality of your code, not the user interface (UI) of your application. However, you can use other types of tests, such as integration tests or end-to-end tests, to test your UI. These tests can also be automated and run using a continuous integration (CI) tool. This can help to catch bugs in your UI early in the development process.

Shaumik DaityariShaumik Daityari
View Author

Shaumik is a data analyst by day, and a comic book enthusiast by night (or maybe, he's Batman?) Shaumik has been writing tutorials and creating screencasts for over five years. When not working, he's busy automating mundane daily tasks through meticulously written scripts!

gitpythonunit testingversion control
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week