How to write unit tests
Discover the essentials of unit testing: key components, best practices, and the advantages of using frameworks and tools like Cody for efficient testing.

Discover the essentials of unit testing: key components, best practices, and the advantages of using frameworks and tools like Cody for efficient testing.
Imagine being in the final stages of a critical project. Everything seems on track, but a bug surfaces, throwing the entire schedule into chaos. According to a study by the IBM System Science Institute, the cost to fix a bug found after product release can be up to 6 times higher than if caught during the design phase. This scenario is all too common in software development.
Catching bugs late in the development process is frustrating and costly. As renowned software developer Martin Fowler once said, "Any fool can write code that a computer can understand. Good programmers write code that humans can understand." Unit testing ensures that code is functional, maintainable, and reliable.
This guide on unit testing will explain why writing unit tests matters, discuss the different unit-test frameworks available, how unit-testing practices have evolved, and provide some pro tips for writing maintainable unit tests. It will also show how Cody can enhance the whole process.
Now, let's break down the details.
Before we discuss the unit test framework, let's understand why unit tests matter.
Unit testing is the practice of isolating individual units or functions of your code. A "unit" refers to the smallest testable part of an application, such as a function, method, or class. Testing at the unit level ensures that each "unit" of your software works correctly. If issues occur, they can be fixed early, saving time and resources.
Unit tests also serve as documentation, providing insight into what the code should do. This is important for future maintenance and other developers who might work on the project. Unit testing frameworks facilitate writing, executing, and managing tests by providing tools and frameworks.
These tools and frameworks offer developers a way to test single functions, often through assert statements or method attributes, and determine whether the test method has passed or failed.
Popular frameworks include:
These frameworks offer features like test discovery, setup and teardown mechanisms, and rich assertion libraries, making it easier to implement and maintain tests for a wide range of software. Next, let's break down the key components of a unit test.
A typical unit test has three key components, each testing the application's functional behavior.
FindClosestBanks function can be triggered with the input, as shown in the code snippet below.Let's look at an example of a unit test in Golang using the testing package, illustrating the three components of a unit test:
func TestFindClosestBanks(t *testing.T) {
// Arrange
banks := []Bank{
{Name: "Bank A", X: 1, Y: 1},
{Name: "Bank B", X: 2, Y: 2},
{Name: "Bank C", X: 5, Y: 7},
}
expected := "Bank A"
// Act
result := FindClosestBanks(banks, 1, 1)
// Assert
if result != expected {
t.Errorf("expected %v, got %v", expected, result)
}
}
In this example, the three components of a unit testing process are implemented:
banks slice and the expected result are set up.FindClosestBanks function is called with the input.t.Errorf if they do not match.Understanding these components is important for writing effective unit tests. Next, let's review the advantages of unit testing to the development process.
The essence of code testing, especially unit testing, goes beyond just verifying the basic functionality of an application; they include:

Some developers might underestimate the importance of unit testing, but let's consider the consequences of discovering bugs late in the development cycle:
As software development evolved, so did unit testing practices. From manual testing to sophisticated mocking frameworks and comprehensive code coverage tools, it is improving to address complex software architectures and agile development demands.

To effectively write good unit tests and maintain them, let's look into the practical tips for creating effective and maintainable unit tests, including code snippets that demonstrate various scenarios (positive, negative, and edge cases)
Below are practical tips and strategies for writing unit tests that check code functionality and ensure long-term maintainability:
Different types of unit test scenarios Unit tests include diverse scenarios, from positive and negative to edge cases. Each scenario validates code behavior under specific circumstances.
Positive scenario The positive scenario tests the add function with typical input values (1 and 2). It verifies that the function returns the expected result of 3 under normal conditions.
func TestPositiveAdd(t *testing.T) {
result := add(1, 2)
if result != 3 {
t.Errorf("Expected 3, but got %d", result)
}
}
Negative scenario For a negative scenario, the add function is tested with unexpected inputs, such as a string ("1") instead of an integer. This test ensures that the function correctly handles such cases by expecting a panic, thus validating error-handling mechanisms.
func TestAddNegative(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Errorf("Expected panic for adding string and int") } }() add("1", 2) }
Edge case The edge case scenario tests the add function with boundary conditions, specifically both operands set to 0.
go func TestEdgeAdd(t *testing.T) { result := add(0, 0) if result != 0 { t.Errorf("Expected 0, but got %d", result) } }
After defining test scenarios, the next step is to run the unit tests and interpret the results, identifying whether tests have passed or failed.
Running unit tests is straightforward with most frameworks. To run unit tests in Go using the testing framework, navigate to your project directory that contains the test and run the go test command:
bash go test
To understand the result, unit test outcomes are categorized into two:
You can automate the entire manual process of writing unit tests and create a maintainable test using Cody; let's see how Cody can optimize and simplify this process.
Manual unit testing can be time-consuming, especially for developers working with large codebases. It's easy to miss crucial edge cases that could lead to bugs. This is where Cody, Sourcegraph's AI coding assistant, comes in. Cody streamlines unit testing with automation features, making the process more efficient and helping developers catch potential issues quickly.
To begin using Cody:

Let's use a Go word search program as an example. It includes a function that takes in three letters to find a word stored in a slice of words, as shown below:
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
func searchWords(words []string, query string) []string {
var results []string
for _, word := range words {
if strings.Contains(word, query) {
results = append(results, word)
}
}
return results
}
func main() {
words := []string{"apple", "banana", "cherry", "elderberry", "fig", "grape", "honeydew", "kiwi", "lemon"}
scanner := bufio.NewScanner(os.Stdin)
fmt.Println("Enter three letters to search for a word:")
scanner.Scan()
query := scanner.Text()
if len(query) != 3 {
fmt.Println("Please enter exactly three letters.")
return
}
results := searchWords(words, query)
if len(results) > 0 {
fmt.Println("Words found:")
for _, result := range results {
fmt.Println(result)
}
} else {
fmt.Println("No words found containing the given letters.")
}
}
Once Cody is installed and set up in your development environment, you can leverage its capabilities to generate unit tests for this Go program.
Writing unit tests with Cody Let's generate unit tests for the Go search program described above in the code snippet by following these steps:
searchWords function.
Cody analyzes the function and automatically generates comprehensive unit test cases, ensuring thorough coverage of various input scenarios. The next step is to review the generated test for modification.
searchWords function behaves as expected under different conditions.

go test framework CLI for Go. Cody integrates with popular testing frameworks, ensuring your tests are executed efficiently.
Unit testing ensures code quality, maintainability, and reliability in software development. Using tools like Cody, developers can master unit testing and enhance their development workflows all the way to production.
Remember, effective unit testing is not just about writing tests - it's about creating a culture of quality and confidence in your codebase.
To explore more features on how Cody can streamline your unit testing process, sign up for a free forever account and take your development workflow to the next level.

With Sourcegraph, the code understanding platform for enterprise.
Schedule a demo