Tidbits | Feb. 3, 2024

Pro-Tip – "En-Rich" your Python testing

by Frank Wiles |   More posts by Frank

Debugging complex code is not usually fun

It's hard to get motivated when faced with a big pile of numbers that just aren't lining up in the ways they are supposed to. Stupid numbers, do what you're told!

The task at hand

I've been rebuilding a 15 to 20-year-old ColdFusion currency trading learning simulation for a client. The math involved is mostly contained in SQL Server stored procedures, some values in tables are really constants (or at least most of the time), and variable names between steps of the process aren't consistent. This has made the rebuilding effort harder than it originally appeared. Add in finding some undesired behaviors in the old app and the desire to improve these situations going forward has been a bit of a slog actually.

I knew that today was the day I had to just dig in and iterate on this code until all of the numbers were correct now that I had a known good set of values to work against.

Since I needed to compare several numbers together, I thought I might use a Rich Table to make the output a little better organized. And I'm VERY glad I did.

Here is what I started with:

Initial boring pytest output

Not exactly pretty. It spit out the numbers it was calculating and I could compare them against the known good values visually. Looking and re-re-reading the numbers a half dozen times I was already bored and feeling tired despite having too much caffeine in my system.

So I implemented all of the expected, calculated and differences between the two in a Rich Table. I took a couple of moments to adjust some colors and right justifying all of these numbers because I knew I was going to be staring at this all freaking day.

Initial Python Rich table formatting

The debugging

I then went about debugging the first Total Unhedged number because all of the following calculations depended on this number being correct. And I quickly found the bug!

I would love to say it was because of this great new visualization, but it was just closely re-reading my Python code and comparing it (yet again) to the original portion of a stored procedure to find my subtle mistake.

One down, 6 more to go I thought. This is going to be a long day, but it turned out two more of the values were NEARLY correct now, just off due to rounding. This WAS easier to spot because of the tabular layout. I treated myself to a bit more table improvement, I wrote a little function named calc_diff() that took the expected and actual values and color coded the difference red if there was a difference and green if not.

Improved table with red/green color coding

Table code example

The code for this is surprisingly easy. Rich was already a dependency of something else in my project's virtualenv so I didn't even need to install it. Here is a simplified example of the code for you:

from decimal import Decimal

from rich.console import Console
from rich.table import Table
from rich.text import Text 

def calc_diff(expected, actual):
        diff = expected - actual

        color = "red"
        if diff == 0:
            color = "green"

        text = Text(str(diff))
        text.stylize(color)

        return text

# My actual pytest test
def test_ex3_math(ex3_params):
    expected_total_unhedged = Decimal("3.394148")

    # call a method on a fixture class that does the math calculation for us, yours
   # would obviously be different 
    total_unhedged = ex3_params.total_unhedged()

    # Build our table
    table = Table(
        title="Exercise 3 Answers", title_style="gold1", style="deep_sky_blue3"
    )
    table.add_column(
        "Name", style="turquoise2", header_style="turquoise2", no_wrap=True
    )
    table.add_column("Expected", header_style="turquoise2", justify="right")
    table.add_column("Actual", header_style="turquoise2", justify="right")
    table.add_column("Difference", header_style="turquoise2", justify="right")

    # Total Unhedged - just showing you one row here for sake of brevity
    table.add_row(
        "Total Unhedged",
        str(expected_total_unhedged),
        str(total_unhedged),
        calc_diff(expected_total_unhedged, total_unhedged),
    )

   # Output the table 
   console = Console(color_system="256")
   console.print(table)

   # Trigger our test to fail artificially 
   assert False

Conclusion

Was it worth the extra time? DEFINITELY! I not only made it easier to compare the values as I worked but also motivated me a little more than usual as I turned each red line green.

Every little bit helps, right? Hopefully, you enjoyed this protip and can put it use in one of your upcoming debugging sessions.

If you or your company struggles with testing your Python or Django apps, check out TestStart our testing consulting package to help jump start your team into better productivity.

Test output is often an afterthought. You can improve your flow and slightly gamify your bug hunting by combining the wonderful Rich Python library and pytest.{% else %}

2024-02-03T10:49:11.736755 2024-02-03T10:49:11.686116 2024