Improving on unit test styles

345 views
Skip to first unread message

Markus Zimmermann

unread,
Feb 25, 2022, 1:48:19 PM2/25/22
to golang-nuts
Hi Gophers!

We were unhappy with the common unit test styles and we think we found a style that has clear advantages. An in-depth comparison can be found here https://symflower.com/en/company/blog/2022/better-table-driven-testing.

We also added support for maintaining such tests in our VS Code extension https://marketplace.visualstudio.com/items?itemName=symflower.symflower. You can either use the command or the context menu item to maintain tests. Here is a short introduction video https://www.youtube.com/watch?v=mgWLY9DDyDE&ab_channel=Symflower

There are some changes necessary to have better stack traces for github.com/stretchr/testify because "t.Run" calls the test function from another location. We are in the process of upstreaming them. Until then you can find them in our fork at https://github.com/symflower/testify.

Would appreciate your feedback on the style and extension. Would be also interesting to hear other approaches and conventions that could help others to write better tests.

Cheers,
Markus

Markus Zimmermann

unread,
Feb 28, 2022, 5:06:50 AM2/28/22
to golang-nuts
First change for github.com/stretchr/testify has been pushed as a PR https://github.com/stretchr/testify/pull/1161: "Include locations of subtests in test failure stack traces". Please give your emoji-support if you agree that this is a good behavior change.

More open source changes are coming. Takes some time because we need to alternate between product work.

Dan Kortschak

unread,
Feb 28, 2022, 5:59:33 AM2/28/22
to golan...@googlegroups.com
On Fri, 2022-02-25 at 09:26 -0800, 'Markus Zimmermann' via golang-nuts
wrote:
> Would appreciate your feedback on the style and extension. Would be
> also interesting to hear other approaches and conventions that could
> help others to write better tests.

I'm struggling to understand how you capture stack traces that are
meaningful to the tested code in any general kind of way — ignoring for
a moment the reduced utility of stack traces in concurrent code. Surely
you get stack traces for the assertion, by which time the interesting
code has passed. Are you doing more than that?


roger peppe

unread,
Feb 28, 2022, 3:48:52 PM2/28/22
to Markus Zimmermann, golang-nuts
One disadvantage of your approach: the table can't be reused for different tests. Not uncommonly I've found that it's useful to be able to plug the same table into more than one testing function - after all, the test data is an abstract description of some property and there can be more than one way of testing that property.

I usually do something half way between the "standard" approach and your suggestion. I have a struct with a "testName" field ("name" is too easily confused with input data IMHO), and I'll use T.Run to run it as a subtest.

I've found that the lack of stack trace is much less of a problem if you use a test name that isn't mangled by the testing package - in other words: don't use spaces! That way a simple text search in your editor will usually take you straight to the relevant table entry.

  cheers,
    rog.

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/4c52a6b6-6762-442c-b9b8-82b8f50a732dn%40googlegroups.com.

twp...@gmail.com

unread,
Mar 3, 2022, 8:50:05 AM3/3/22
to golang-nuts
To identify failing test cases in a table of tests where you don't have individual names you can use its index:

    for i, testCase := range testCases {
        t.Run(strconv.Itoa(i), func(t *testing.T) {
            // ...

For debugging an individual test case, just skip over all but the failing test case:

    for i, testCase := range testCases {
        if i != 5 { // 5 is the index of the failing test, remove if statement before committing
            continue
        }
        t.Run(strconv.Itoa(i), func(t *testing.T) {
            // ...

It's a quick cheap hack, but occasionally useful.

Dan Kortschak

unread,
Mar 4, 2022, 4:17:07 AM3/4/22
to golan...@googlegroups.com
On Thu, 2022-03-03 at 05:50 -0800, twp...@gmail.com wrote:
> For debugging an individual test case, just skip over all but the
> failing test case:
>
> for i, testCase := range testCases {
> if i != 5 { // 5 is the index of the failing test, remove if
> statement before committing
> continue
> }
> t.Run(strconv.Itoa(i), func(t *testing.T) {
> // ...
>
> It's a quick cheap hack, but occasionally useful.

This can be done with `go test -run ^TestTheTest/5$`. Meaningful names
make it even better.



Reply all
Reply to author
Forward
0 new messages