Hone logo
Hone
Problems

Mastering Go Unit Testing: A Foundational Challenge

This challenge will guide you through the fundamental principles of writing unit tests in Go. Understanding how to test your code is crucial for building robust, reliable, and maintainable applications. You will learn to define test functions, assert expected outcomes, and handle different scenarios.

Problem Description

Your task is to implement a set of unit tests for a simple Go function that performs basic arithmetic operations. You will be provided with a Go file containing a Calculator struct with methods for Add, Subtract, Multiply, and Divide. Your goal is to write tests for these methods to ensure they behave as expected under various conditions.

Key Requirements:

  1. Test Suite Creation: Create a new Go file (e.g., calculator_test.go) in the same package as the calculator.go file.
  2. Test Functions: Implement individual test functions for each method of the Calculator struct. Test function names must start with Test.
  3. Assertions: Use Go's built-in testing package, specifically t.Errorf or t.Fatalf, to report test failures.
  4. Test Cases: Cover a range of input scenarios, including:
    • Positive numbers
    • Negative numbers
    • Zero
    • Division by zero (expected to panic or return an error)

Expected Behavior:

  • Successful tests should not produce any output when run with go test.
  • Failed tests should clearly indicate the failing test function, the input, the expected output, and the actual output.
  • The division method should handle division by zero gracefully, either by panicking (and you should test for that panic) or by returning an error.

Examples

Example 1: Testing Add

Let's assume the calculator.go file has the following Add method:

func (c *Calculator) Add(a, b int) int {
	return a + b
}

Test File (calculator_test.go):

package main

import "testing"

func TestAdd(t *testing.T) {
	// Test case: positive numbers
	result := calc.Add(5, 3)
	expected := 8
	if result != expected {
		t.Errorf("Add(5, 3) = %d; want %d", result, expected)
	}

	// Test case: negative numbers
	result = calc.Add(-2, -4)
	expected = -6
	if result != expected {
		t.Errorf("Add(-2, -4) = %d; want %d", result, expected)
	}

	// Test case: mixed numbers
	result = calc.Add(10, -5)
	expected = 5
	if result != expected {
		t.Errorf("Add(10, -5) = %d; want %d", result, expected)
	}
}

Running the tests:

If all assertions pass, go test will produce no output. If, for instance, calc.Add(5, 3) returned 7 instead of 8, the output would be:

--- FAIL: TestAdd (0.00s)
    calculator_test.go:7: Add(5, 3) = 7; want 8
FAIL
exit status 1
FAIL    your_module_path/calculator    0.001s

Example 2: Testing Divide with Zero

Let's assume the calculator.go file has the following Divide method (designed to panic on division by zero):

func (c *Calculator) Divide(a, b int) (int, error) {
	if b == 0 {
		panic("division by zero")
	}
	return a / b, nil
}

Test File (calculator_test.go):

package main

import (
	"testing"
	"errors"
)

func TestDivideByZero(t *testing.T) {
	// Defer a function to recover from the panic
	defer func() {
		if r := recover(); r == nil {
			t.Errorf("The code did not panic when expected")
		} else if r.(string) != "division by zero" {
			t.Errorf("Expected panic message 'division by zero', but got %v", r)
		}
	}()

	// This call should trigger the panic
	calc.Divide(10, 0)
}

Running the tests:

If the panic occurs as expected, go test will produce no output for this specific test.

Example 3: Testing Divide with Error Return

Alternatively, if Divide returns an error:

func (c *Calculator) Divide(a, b int) (int, error) {
	if b == 0 {
		return 0, errors.New("division by zero")
	}
	return a / b, nil
}

Test File (calculator_test.go):

package main

import (
	"testing"
	"errors"
)

func TestDivideWithError(t *testing.T) {
	result, err := calc.Divide(10, 0)
	if err == nil {
		t.Errorf("Expected an error for division by zero, but got nil")
	}
	if result != 0 {
		t.Errorf("Expected result 0 for division by zero, but got %d", result)
	}

	// Test successful division
	result, err = calc.Divide(10, 2)
	if err != nil {
		t.Errorf("Did not expect an error for division by non-zero: %v", err)
	}
	expected := 5
	if result != expected {
		t.Errorf("Divide(10, 2) = %d; want %d", result, expected)
	}
}

Constraints

  • You are expected to use the standard Go testing package.
  • All test functions must be in a file ending with _test.go.
  • Input integers for arithmetic operations will be within the range of standard int type.
  • The Divide function's behavior on division by zero (panic vs. error) will be specified in the provided calculator.go file. You must test for that specific behavior.

Notes

  • To run your tests, navigate to the directory containing your Go files in the terminal and execute the command: go test.
  • You might need to initialize a Calculator instance within your test file (e.g., var calc = &Calculator{}).
  • Consider using table-driven tests for more complex scenarios to make your tests more concise and readable. However, for this foundational challenge, individual test functions are sufficient.
  • The testing package provides useful functions like t.Helper() to mark functions as test helpers, which can improve error reporting. You might explore this for more advanced testing.
  • Success looks like running go test without any output, indicating all tests have passed.
Loading editor...
go