Guidance on tests

Hello everyone,

i have just started a go project and i’m having some doubts about the better approach to test some scenarios.

Code snippet:

func (e *env) processFile() {
	scanner := bufio.NewScanner(e.fd)
	for scanner.Scan() {
		line := scanner.Text()
		count := processLine(line)
		e.loaded += count
	}

	if err := scanner.Err(); err != nil {
		log.Printf("Error line processing: %s\n", err)
	}
}

I implemented tests for my package and looking at coverage report i see the following:

Which are the best ways to handle scenarios like that? Is there any other strategy available to reach this coverage or it shouldn’t be something that i need to be concerned of?

Thanks in advance,

Rafael

Hi, Rafael, welcome to the forum!

Tests should not only check the behavior of good input but also bad input. Include tests in your test cases that produce errors so that you’re sure you don’t have any bugs in your error handling.

Is pattern a const regular expression pattern string? If that’s the case, I would recommend changing pattern from something like

const pattern = `my regexp pattern`

to something like

var pattern = regexp.MustCompile(`my regexp pattern`)

regexp.Match is a convenience function that calls regexp.Compile on your regular expression, and then (*regexp.Regexp).Match on the result. This means every time it’s executed, your regular expression is (re)compiled, so if you expect the regular expression to be used more than once, it would be better to compile it only once with an explicit call to regexp.Compile (or regexp.MustCompile which will panic on error) and then call the Match member function on your compiled *regexp.Regexp after that.

I suggest regexp.MustCompile if pattern is a const expression string because given your testing, it seems the expression is correct and so err will always be nil. (*regexp.Regexp).Match only returns a bool, so that will eliminate the error check and get you to 100% coverage of this function.

tl;dr

Change

const pattern = `myregex`

func processLine(line string) int {
    count := 0
    matched, err := regexp.Match(pattern, []byte(line))

    if err != nil {
        log.Printf("Error processing line %s: %s", line, err)
    }

    if matched {
        log.Printf("Matched: %s", line)
        err = setEnv(line)

        if err == nil {
            count++
        }
    }

    if !matched {
        log.Printf("Not matched: %s", line)
    }

    return count
}

To:

var pattern = regexp.MustCompile(`myregex`)

func processLine(line string) int {
    count := 0
    matched := pattern.MatchString(line)

    if matched {
        log.Printf("Matched: %s", line)
        err = setEnv(line)

        if err == nil {
            count++
        }
    }

    if !matched {
        log.Printf("Not matched: %s", line)
    }

    return count
}
1 Like

Hi skillian, very glad to hear from you!

I believe i got your point. My approach wasn’t the best one.

As i have pattern as a const, it does not allow me to test for inconsistent patterns input.Changing the approach and using regexp.MustCompile i can have a consistent and testable scenario.

Bottom line, makes sense that, if i don’t have a clear way to handle cases like that (without per example changing the source code to match the errors cases) probably a cleaner way is possible.

If i can, I would like to discuss also one other scenario ( both examples to me boils down to the same aspect ):

Processing a line:

func processLine(line string) int {
count := 0
matched := pattern.MatchString(line)

if matched {
	log.Printf("Matched: %s", line)
	// Strip eventuals " chars

	if err := setEnv(strings.ReplaceAll(line, "\"", "")); err != nil {
		log.Fatalf("Error processing line %s: %s", line, err)
	}
	count++
}

if !matched {
	log.Printf("Not matched: %s", line)
}

return count
}

Processing a file:

func (e *env) processFile() {
scanner := bufio.NewScanner(e.fd)
for scanner.Scan() {
	line := scanner.Text()
	count := processLine(line)
	e.loaded += count
}
if err := scanner.Err(); err != nil {
	log.Printf("Error line processing: %s\n", err)
}
}

Is there anywhere at the documentationi could consult input data that gives me error at scanner.Errr and SetEnv (i’m almost sure this information shouldn’t be at the documentation, but not found it easy way to have the “error” data).

So far i understand that’s the only way to test the error handling cases for scenarios like that:

Or there are ways to stub behavior for functions and force an error?

Thanks in advance for the great discussion.

Regards,

Rafael

Hello,

Not sure yet if its the best approach but i was able to achieve 100% coverage at this scenarios changing up a little bit my approach and using mocks on Testify.

Basically, what i did was the create a new level of abstraction using interfaces which allowed me to control the return on the desired scenario.

Thanks again @skillian for the thoughts on the issue.

Regards,

Thx for sharing! You are right!