To effectively test command-line applications, you can leverage the os/exec package to simulate running commands and the testing package to structure your tests. This article will guide you through writing tests for a simple CLI application that performs basic arithmetic operations.

Example CLI Application

Let’s start with a simple CLI application that performs addition and subtraction. Here’s the code for our application:

// calculator.go
package main

import (
    "fmt"
    "os"
    "strconv"
)

func main() {
    if len(os.Args) < 4 {
        fmt.Println("Usage: calculator <add|subtract> <num1> <num2>")
        return
    }

    operation := os.Args[1]
    num1, err1 := strconv.Atoi(os.Args[2])
    num2, err2 := strconv.Atoi(os.Args[3])

    if err1 != nil || err2 != nil {
        fmt.Println("Invalid numbers provided.")
        return
    }

    switch operation {
    case "add":
        fmt.Println(num1 + num2)
    case "subtract":
        fmt.Println(num1 - num2)
    default:
        fmt.Println("Unknown operation. Use 'add' or 'subtract'.")
    }
}

Setting Up Tests

To test this application, we will create a test file named calculator_test.go. We will use the os/exec package to execute our CLI commands and capture their output.

Test Functions

Here’s how you can structure your tests:

// calculator_test.go
package main

import (
    "bytes"
    "os/exec"
    "testing"
)

func runCalculator(args ...string) (string, error) {
    cmd := exec.Command("go", "run", "calculator.go", args...)
    var out bytes.Buffer
    cmd.Stdout = &out
    err := cmd.Run()
    return out.String(), err
}

func TestAdd(t *testing.T) {
    output, err := runCalculator("add", "3", "4")
    if err != nil {
        t.Fatalf("Expected no error, got %v", err)
    }
    expected := "7\n"
    if output != expected {
        t.Errorf("Expected %q, got %q", expected, output)
    }
}

func TestSubtract(t *testing.T) {
    output, err := runCalculator("subtract", "10", "4")
    if err != nil {
        t.Fatalf("Expected no error, got %v", err)
    }
    expected := "6\n"
    if output != expected {
        t.Errorf("Expected %q, got %q", expected, output)
    }
}

func TestInvalidOperation(t *testing.T) {
    output, err := runCalculator("multiply", "3", "4")
    if err != nil {
        t.Fatalf("Expected no error, got %v", err)
    }
    expected := "Unknown operation. Use 'add' or 'subtract'.\n"
    if output != expected {
        t.Errorf("Expected %q, got %q", expected, output)
    }
}

func TestInvalidNumbers(t *testing.T) {
    output, err := runCalculator("add", "three", "four")
    if err == nil {
        t.Fatalf("Expected an error, got nil")
    }
    expected := "Invalid numbers provided.\n"
    if output != expected {
        t.Errorf("Expected %q, got %q", expected, output)
    }
}

Explanation of the Tests

  1. runCalculator Function: This helper function abstracts the command execution logic, allowing us to run our CLI application with different arguments and capture the output.
  1. TestAdd: This test checks if the addition operation produces the correct output.
  1. TestSubtract: This test verifies the subtraction operation.
  1. TestInvalidOperation: This test ensures that an unknown operation returns the appropriate error message.
  1. TestInvalidNumbers: This test checks the application’s response to invalid number inputs.

Running the Tests

To run your tests, simply execute the following command in your terminal:

go test

This command will automatically find and run all test functions in your calculator_test.go file.

Best Practices for Testing CLI Applications

Best PracticeDescription
Use Helper FunctionsCreate functions to encapsulate command execution to avoid code duplication.
Validate Output ThoroughlyAlways check the output against expected results, including error messages.
Test Edge CasesConsider how your application behaves with unexpected input or arguments.
Keep Tests IndependentEnsure that tests do not rely on the state left by other tests.
Use Descriptive Test NamesName your tests clearly to indicate what functionality they are verifying.

Conclusion

Testing command-line applications in Go requires a structured approach to ensure that all functionalities are verified. By leveraging the os/exec package to simulate command execution and capturing output, you can create comprehensive tests that validate the behavior of your CLI applications.

Learn more with useful resources: