What is unit testing?
Unit testing is the process where you test the smallest functional unit of code. Software testing helps ensure code quality, and it's an integral part of software development. It's a software development best practice to write software as small, functional units then write a unit test for each code unit. You can first write unit tests as code. Then, run that test code automatically every time you make changes in the software code. This way, if a test fails, you can quickly isolate the area of the code that has the bug or error. Unit testing enforces modular thinking paradigms and improves test coverage and quality. Automated unit testing helps ensure you or your developers have more time to concentrate on coding.
What is a unit test?
A unit test is a block of code that verifies the accuracy of a smaller, isolated block of application code, typically a function or method. The unit test is designed to check that the block of code runs as expected, according to the developer’s theoretical logic behind it. The unit test is only capable of interacting with the block of code via inputs and captured asserted (true or false) output.
A single block of code may also have a set of unit tests, known as test cases. A complete set of test cases cover the full expected behavior of the code block, but it’s not always necessary to define the full set of test cases.
When a block of code requires other parts of the system to run, you can’t use a unit test with that external data. The unit test needs to run in isolation. Other system data, such as databases, objects, or network communication, might be required for the code’s functionality. If that's the case, you should use data stubs instead. It’s easiest to write unit tests for small and logically simple blocks of code.
Unit testing strategies
To create unit tests, you can follow some basic techniques to ensure coverage of all test cases.
Logic checks
Does the system perform the right calculations and follow the right path through the code given a correct, expected input? Are all paths through the code covered by the given inputs?
Boundary checks
For the given inputs, how does the system respond? How does it respond to typical inputs, edge cases, or invalid inputs?
Let’s say you expect an integer input between 3 and 7. How does the system respond when you use a 5 (typical input), a 3 (edge case), or a 9 (invalid input)?
Error handling
When there are errors in inputs, how does the system respond? Is the user prompted for another input? Does the software crash?
Object-oriented checks
If the state of any persistent objects is changed by running the code, is the object updated correctly?
Unit test example
Here is an example of a very basic method in Python and some test cases with corresponding unit test code.
Python method
def add_two_numbers(x, y):
return x + y
Corresponding unit tests
def test_add_positives():
result = add_two_numbers(5, 40)
assert result == 45
def test_add_negatives():
result = add_two_numbers(-4, -50)
assert result == -54
def test_add_mixed():
result = add_two_numbers(5, -5)
assert result == 0
What are the benefits of unit testing?
Unit testing benefits software development projects in many ways.
Efficient bug discovery
If there are any input, output, or logic-based errors within a code block, your unit tests help you catch them before the bugs reach production. When code changes, you run the same set of unit tests—alongside other tests such as integration tests—and expect the same results. If tests fail (also called broken tests) it indicates regression-based bugs.
Unit testing also helps finds bugs faster in code. Your developers don’t spend a large amount of time on debugging activities. They can quickly pinpoint the exact part of the code that has an error.
Documentation
It's important to document code to know exactly what that code is supposed to be doing. That said, unit tests also act as a form of documentation.
Other developers read the tests to see what behaviors the code is expected to exhibit when it runs. They use the information to modify or refactor the code. Refactoring code makes it more performant and well-composed. You can run the unit testing again to check that code works as expected after changes.
How do developers use unit tests?
Developers use unit tests at various stages of the software development lifecycle.
Test-driven development
Test-driven development (TDD) is when developers build tests to check the functional requirements of a piece of software before they build the full code itself. By writing the tests first, the code is instantly verifiable against the requirements once the coding is done and the tests are run.
After completing a block of code
Once a block of code is considered complete, unit tests should be developed if they have not been already thanks to TDD. Then, you can immediately run unit tests to verify the results. Unit tests are also run as part of the full suite of other software tests during system testing. They're typically the first set of tests that run during full system software testing.
DevOps efficiency
One of the core activities in the application of DevOps to software development practices is continuous integration and continuous delivery (CI/CD). Any changes to the code are automatically integrated into the wider codebase, run through automated testing, and then deployed if the tests pass.
Unit tests make up part of the test suite alongside integration testing. They run automatically in the CI/CD pipeline to ensure code quality as it is upgraded and changed over time.
When is unit testing less beneficial?
Unit testing isn’t always required for every single test case in every single block of code in every single project. Here are some examples of when unit testing could potentially be omitted.
When time is constrained
Even with generative unit testing frameworks, writing new unit tests takes a significant amount of your developers' time. While input and output-based unit tests may be easy to generate, logic-based checks are more difficult.
Once your developers start writing tests, they also see refactoring opportunities in the block of code and get distracted from completing them. This can lead to extended development timelines and budget issues.
UI/UX applications
When the main system is concerned with look and feel rather than logic, there may not be many unit tests to run. Other types of testing, such as manual testing, are a better strategy than unit testing in these cases.
Legacy codebases
Writing tests to wrap around existing legacy code can prove to be near impossible, depending on the style of the written code. Because unit tests require dummy data, it can also be too time-intensive to write unit tests for highly interconnected systems with a lot of data parsing.
Rapidly evolving requirements
Depending on the project, the software can grow, change directions, or have whole parts scrapped altogether in any given work sprint. If requirements are likely to change often, there's not much reason to write unit tests each time a block of code is developed.
What are unit testing best practices?
We give some unit testing best practices to get the most out of your process.
Use a unit test framework
It wastes time to write explicit, fully customized unit tests for every single block of code. There are automated testing frameworks for every popular programming language.
For instance, Python has pytest and unittest as two different frameworks for unit testing. Testing frameworks are used extensively throughout software development projects of all sizes.
Automate unit testing
Unit testing should be triggered on different events within software development. For example, you can use them before you push changes to a branch using version control software or before you deploy a software update.
Unit testing may also run on a complete project, set on a timed schedule. Automated unit testing ensures tests run in all appropriate events and cases throughout the development lifecycle.
Assert once
For each unit test, there should only be one true or false outcome. Make sure that there is only one assert statement within your test. A failed assert statement in a block of multiple ones can cause confusion on which one produced the issue.
Implement unit testing
Unit testing is an important part of building software, but many projects don’t dedicate resources to it. When projects start as prototypes, are small community-based efforts, or are simply coded quickly, unit testing can be left out due to time constraints.
However, when you build projects with unit testing as a standard practice from the beginning, the process becomes far easier to follow and repeat.
What's the difference between unit testing and other types of testing?
There are many other types of software test methods alongside unit testing. They all have specific roles to play in the software development lifecycle:
- Integration testing checks that different parts of the software system that are designed to interact do so correctly.
- Functional testing checks whether the software system passes the software requirements outlined before building.
- Performance testing checks whether the software runs to expected performance requirements, such as speed and memory size.
- Acceptance testing is when the software is tested manually by stakeholders or user groups to check whether it is working as they anticipate.
- Security testing checks the software against known vulnerabilities and threats. This includes analysis of the threat surface, including third-party entry points to the software.
These testing methods usually require specialized tools and independent processes to check the software. Many of them are also performed after basic application functionality has been developed.
In contrast, unit tests run every time the code builds. They can be written as soon as any code is written and don’t require any special tools to run. Unit testing is considered to be one of the most basic types of software testing.
How can AWS help with your unit testing requirements?
Amazon Web Services (AWS) provides developers with a huge range of advantages. You can develop and run code and test software, like through unit testing and integration tests. You can also run DevOps pipelines and pursue many development opportunities.
AWS developer tools offer integrated development environments (IDEs), plugins, and SDKs for several programming languages and programming uses cases. Among other benefits, these tools make unit testing more efficient.
AWS Fargate is a serverless, pay-as-you-go compute engine that lets you focus on building applications without managing servers. You can easily run automated unit testing software on Fargate to streamline your application development.
You can also find third-party unit testing software on the AWS Marketplace. You can implement software quickly and with the controls you need. AWS Marketplace sellers offer flexible pricing options, so you can pay for what you need when you need it.
Get started with unit testing on AWS by creating an account today.