diff --git a/README.md b/README.md index f1382be..21979f5 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ working across different projects via [VisualMode](https://www.visualmode.dev/). For a steady stream of TILs, [sign up for my newsletter](https://visualmode.kit.com/newsletter). -_1782 TILs and counting..._ +_1783 TILs and counting..._ See some of the other learning resources I work on: @@ -1058,6 +1058,7 @@ If you've learned something here, support my efforts writing daily TILs by - [Create A Dummy DataFrame In Pandas](python/create-a-dummy-dataframe-in-pandas.md) - [Create A Range Of Descending Values](python/create-a-range-of-descending-values.md) - [Deduplicate A List Into A Tuple](python/deduplicate-a-list-into-a-tuple.md) +- [Define Sequence Of Tests With Parametrize Decorator](python/define-sequence-of-tests-with-parametrize-decorator.md) - [Dunder Methods](python/dunder-methods.md) - [Easy Key-Value Aggregates With defaultdict](python/easy-key-value-aggregates-with-defaultdict.md) - [Install With PIP For Specific Interpreter](python/install-with-pip-for-specific-interpreter.md) diff --git a/python/define-sequence-of-tests-with-parametrize-decorator.md b/python/define-sequence-of-tests-with-parametrize-decorator.md new file mode 100644 index 0000000..44c47d0 --- /dev/null +++ b/python/define-sequence-of-tests-with-parametrize-decorator.md @@ -0,0 +1,60 @@ +# Define Sequence Of Tests With Parametrize Decorator + +I have a function that I want to test across a bunch of different inputs. That +way I can make sure the logic of that function handles all the different +scenarios I have in mind. + +While working on [`py-vmt`](https://github.com/jbranchaud/py-vmt), I started by +writing a big single test function with a sequence of variable assignments and +`assert` statements. Here's my starting point: + +```python +def test_format_time_delta_everything(): + # less than a minute + thirty_seconds = timedelta(seconds=30) + assert "30s" == format_time_delta(thirty_seconds) + + # one minute exactly + one_minute = timedelta(seconds=60) + assert "1m" == format_time_delta(one_minute) + + # more than a minute + assert "1m30s" == format_time_delta(one_minute + thirty_seconds) + + # bunch of minutes and seconds + delta = timedelta(minutes=24, seconds=8) + assert "24m8s" == format_time_delta(delta) + + # one hour exactly + one_hour = timedelta(hours=1) + assert "1h" == format_time_delta(one_hour) + + # more than one hour + assert "1h24m" == format_time_delta(one_hour + delta) +``` + +I knew I would eventually need to break it up into individual test functions, +but I couldn't bare to start there because it seemed quite repetitive. + +There is another way to approach this without all the duplication. Pytest comes +with [a "parametrize" decorator](https://docs.pytest.org/en/stable/example/parametrize.html). This is +used to define a set of test data (and expected values) that will get passed +one-by-one to the test function as parameters. + +```python +@pytest.mark.parametrize("input,expected", [ + (timedelta(seconds=30), "30s"), + (timedelta(seconds=60), "1m"), + (timedelta(seconds=90), "1m30s"), + (timedelta(minutes=24, seconds=8), "24m8s"), + (timedelta(hours=1), "1h"), + (timedelta(hours=1, minutes=24, seconds=8), "1h24m"), +]) +def test_format_time_delta(input, expected): + assert format_time_delta(input) == expected +``` + +I ditch all of the duplication this way. I define a list of tuples that +represent my input values and expected values. Then the body of the test can be +minimal. And I get a separate test execution for each parameter tuple making it +easier to see fine-grained pass/fail results.