Featured Webinar: MISRA C++ 2023: Everything You Need to Know | Watch Now

Increase Unit Testing ROI With Automatic Unit Test Creation

Headshot of Brian McGlauflin,
June 16, 2023
10 min read

Use the Parasoft Jtest automated unit test generation tool to boost your unit testing ROI. Read on to learn how to automate the tedious aspects of unit testing and reduce mocking complexity.

Parasoft Jtest’s automatic unit test creation technology removes unit testing roadblocks, automating the mundane aspects of unit testing, including creation, isolation, mocking, and maintenance.

Most development teams will agree that, although they don’t like it, unit testing is, in fact, extremely valuable. Despite the many tools that help with manual test creation, creating and maintaining unit tests still requires a lot of manual, time-consuming, often mind-numbing effort before adding business logic to a test. As such, despite having the intention of great unit testing, development teams typically do the minimal amount of unit testing required or skip it altogether.

Top Unit Testing Concerns of Java Developers

A recent customer survey revealed, somewhat expectedly, that test creation, mocking/isolation, and maintenance are key issues standing in the way of unit testing success. Figure 1 shows the results of this survey, showing the top concerns developers are identifying with unit testing in Java.

Graph showing survey results of Java Developer's Key Areas of Concern: Creation Time (58%), Isolation and Mocking (65%), Maintenance and Upkeep (65%), Execution Time (8%).
Survey results show key areas of concern as a percentage that arise from unit testing.

To address these main areas of concern, we can turn to automated software testing. In this blog, I’ll show you how to use Parasoft Jtest to automatically create unit tests, to save time and energy at test creation time. Parasoft Jtest’s automatic unit test creation technology sets up the test framework, instantiates objects, configures mocks for appropriate objects and method calls used by the method under test, and adds assertions. It creates regular JUnits, getting all the mundane work done so developers can focus on applying human intelligence and logic to their testing.

Understanding the Fundamentals of Unit Testing

Every software engineer who understands the need to prioritize high-quality and bug-free code in software development will tell you that unit testing is like a bedrock upon which other aspects of software quality assurance are built. Unit testing ensures that individual units or components of code work as expected. It involves testing a small piece of code, typically a single function or method, in isolation from the rest of the system.

To ensure that each unit of code works as intended and performs the expected function assigned to it, developers write test cases either manually or with automatic unit test creation tools. Running these tests helps to identify any bugs or errors in the code before it’s integrated into the larger system. Running them regularly ensures that what works today continues to work in the future.

Unit testing has become increasingly important over the years, as software systems have become more complex and the need for reliable software has increased. However, to get the most value from unit tests, there are recommended policies and best practices that should guide the process.

  1. Write test cases early enough. It’s important to write your test cases early in the development process. Ideally, as you write code or even before you write code, which is known as test-driven development or TDD. This enables developers to catch bugs and issues early when they’re easier and cheaper to fix. This approach is also vital in ensuring that the software is being built to meet the requirements and specifications laid out in the design. Writing tests alongside code keeps developers thinking about quality throughout the development process.
  2. Unit tests should be written to be specific and independent. Given that the primary goal of unit testing is to test single components or units of code, tests should be specific and independent of each other. As such, each test should focus on testing a specific feature or aspect of the code and should not rely on the results of other tests.
  3. Use test automation. Test automation involves using software tools to automatically write test cases, execute tests, and evaluate the results. Using test automation tools can save time and increase the efficiency of the testing process. It also allows for tests to be run more frequently, ensuring that any issues or bugs are caught early on.

For instance, Parasoft’s automated unit testing solution offers software developers AI-driven unit test generation that can be deployed to auto-generate robust and ready-to-run unit test cases quickly. It also provides IDE and CI/CD tools for running, extending, and maintaining those tests as code changes, and analysis of important metrics like code coverage.

  1. Use mocks for test isolation. Mocks allow developers to isolate the code being tested from other components or systems, making it easier to configure tests to validate specific use cases, identify issues, and isolate bugs. Using mocks also allows tests to be run more quickly and efficiently. Parasoft’s automated unit testing tool uses mocks in newly generated tests and provides additional tools to help users create, update, and manage mocks in their tests for easy test isolation.
  2. Use code coverage metrics. Code coverage metrics measure the degree to which the software’s source code is tested to determine whether the system has been sufficiently tested or not. Using code coverage metrics can identify areas of the code that are not being tested and ensure that all code is being tested thoroughly. Parasoft tools can collect, aggregate, and analyze code coverage gathered from multiple testing levels to assist developers in thoroughly testing their applications. Users can focus on modified code coverage to optimize their testing efforts and fill gaps in coverage earlier in the software development workflow.

Overcoming Key Challenges in Unit Testing

While unit testing is crucial in every software development endeavor, it can also present several challenges that hinder its effectiveness. Below are some key challenges in unit testing and how to overcome them.

  1. Achieving appropriate levels of code coverage efficiently. To ensure that code is sufficiently tested, developers should prioritize testing critical and complex code paths and use code coverage tools to identify areas that need additional testing. The definition of what is sufficient testing can be up for debate, but a common and reasonable goal is 80% code coverage.

The challenge is that it can take a large amount of time and effort to achieve high levels of code coverage and unit testing often gets deprioritized in the face of schedules and pressure to develop new functionality. Using an automated tool like Parasoft Jtest can significantly reduce the amount of time it takes to create and maintain unit tests, making the creation of a sufficient number of unit tests achievable.

  1. Enforcing good development practices on updated code. It’s common for development teams to update functionality in applications that do not have enough tests. This introduces a lot of risks since tests serve as a safety net to ensure the application behavior does not change unexpectedly. Teams can mitigate this risk by measuring modified code coverage, which is a calculation of the amount of modified code (both new code and old code that was changed) that is covered by tests. By enforcing sufficient levels of modified coverage with Parasoft Jtest, development teams can ensure that their modified code is well-tested, even if large portions of the rest of the application are not.
  2. Running appropriate tests to verify code changes. Developers should run existing tests in their IDE when they make code changes to ensure that their changes do not break existing functionality. However, if their codebase is large, they may not understand which tests need to be run, so they may skip running tests before committing code.

Furthermore, after committing code, the CI/CD process needs to execute unit tests to verify the changes. When there is a large suite of tests, they may not execute as quickly as desired. Parasoft Jtest identifies impacted tests that need to be run when code changes, in both the IDE and CI/CD process so that developers can identify and run an optimized set of changes that verify their changes.

  1. Utilizing the proper kinds of tests for the code under test. Unit tests are not always the optimal types of tests to use to cover specific portions of the code. Unit testing is just one type of testing and should be complemented by other testing strategies, such as API testing, functional testing, UI testing, performance testing, and security testing. Utilizing unit testing along with other testing strategies requires investing in appropriate tools and frameworks. Teams should collect code coverage data from all their testing practices, to give themselves a holistic view of coverage across all their tests – which allows them to optimize their efforts to use the most appropriate testing strategy depending on the code they are testing.
  2. Writing testable code. Testable code is code that can be easily exercised by unit tests and tested in isolation without relying on external dependencies. Code that relies heavily on external dependencies or states can be difficult to test, leading to fragile and unreliable tests. To overcome this challenge, developers can aim to write modular and loosely coupled code. Frameworks such as Mockito can also be used in tests to help isolate the code under test from its dependencies.

Benefits of Unit Testing & Why Developers Still Don’t Do Enough

Unit testing is iterative, and in Agile processes, unit testing has a good track record of improving project outcomes. It’s well-proven that testing earlier in the life cycle is the best way to detect and remove expensive and time-consuming system-level bugs. More specifically, the following are some key benefits of unit testing.

  • Providing agility for Agile processes. Agile processes depend on efficient, repeatable, and automated test suites in order to ensure that each iteration isn’t bogged down in a “big bang” test cycle. The success of Agile and DevOps very much depends on development teams creating test suites that can be run efficiently, as well as testing as much functionality as possible.
  • Improved quality and security. Software teams understand that testing is the only way to ensure software is doing what’s required, but teams often fail to do enough testing or do it too late in the development life cycle. Security, in particular, needs to be built into an application at the earliest stages, so testing for security must be done as early as possible.
  • Reducing costs in the long run. Software bugs found in released software can be significantly more expensive to fix than during development. Bugs found at early coding stages in unit testing are much cheaper to remediate than those found later. Short-term investment in testing automation and unit test infrastructure pays off in better product quality, security, and on-time delivery.

Unfortunately, regardless of these benefits, developers still struggle with unit testing, despite the desire to achieve better results. These struggles, also illustrated by the survey results above, include the following.

  • Test creation is extra work and often tedious. Understandably, unit testing is extra work and is often seen as the least desirable aspect of programming. Creating a comprehensive test suite while trying to maintain project goals and deadlines are two competing pressures for development teams.
  • Test maintenance is expensive. Just like code, unit tests require maintenance. Any code change may introduce changes in associated tests. Not only that but also tests that fail due to code base modifications might be seemingly unrelated, leading to test suite instability. Extra maintenance creates double the work in many developers’ minds.
  • Mocking and isolation of units under test are difficult and time-consuming. It’s critical to isolate units under test, but doing so requires mocking of dependencies, which can be a time-consuming process.

Software development teams must address these problems with test creation, isolation, and maintenance if they want to achieve the benefits of thorough unit testing, and the solution is automation. Parasoft Jtest’s automatic unit test creation tool provides the assistance teams need for test creation and maintenance while working within the developer’s IDE and leveraging existing test and mocking frameworks.

Reduce Burden With Automatic Test Creation

Creating unit tests is tedious and steers attention away from the more interesting parts of a project. The code itself is repetitive and often requires as much effort as the code under test. On top of that, the unit test code itself requires fixing and debugging, as any code would.

Fortunately, unit tests lend themselves well to automation. Automatic guidance from a unit testing tool has its advantages.

  • Simplifies test creation greatly.
  • Reduces the amount of debugging and fixing.
  • Collects results and metrics to feed project analytics.

Going Beyond the IDE

Many IDEs provide unit test creation wizards for JUnit, for example, but don’t provide “content” to complete the process. Assertions need to be manually defined, and if mocking frameworks are used, a significant amount of manual coding is required.

Instead, Parasoft Jtest provides real-time, context-aware assistance in the developer’s IDE. With automatic unit test creation, the content missing from simple skeleton unit tests is quickly and efficiently completed as you leverage your unit testing tool to perform the following:

  • Create a test skeleton, instantiate objects, configure appropriate mock objects and methods, and add assertions.
  • Identify method calls that should be mocked to better isolate the code under test.
  • Detect system resources created but not freed after test completion, potentially creating an unstable test environment.
  • Collect code coverage and other metrics to assess the code health and identify coverage gaps.
  • Optimize the test suite by automatically removing newly generated tests that do not contribute additional coverage

To address this, let’s look at an example. Here, Parasoft Jtest’s Unit Test Assistant is used within an IDE to create a suite of tests:

Screenshot showing the Create test suite menu option in Parasoft Jtest's Unit Test Assistant used within an IDE.

The tool creates multiple tests, each configured to cover a separate path through the method under test. For example, the generated code may look like this:

Screenshot showing code generated by Parasoft Jtest's Unit Test Assistant.

Note that the test is pre-configured with mocked objects and real values, as well as assertions on the result of the method under test and any objects that changed during execution (in this case, fields in the object under test changed).

Reduce Mocking Complexity

Unit testing implies isolation of the object under test, which requires a fair amount of work if there are many dependencies. Even with mocking frameworks such as Mockito, there’s still significant manual coding required. With an automated test assistant tool, you can detect dependencies and automatically fill in the details required by the framework.

The tool itself analyzes the code under test, automatically detects dependencies, and makes recommendations to the developer.

For example, assume we execute a unit test containing the following code that mocks the class IWorkspaceClass:

Dependencies are detected during runtime and the tool recommends mocking the dependencies that it has found:

Selecting “Mock It” with the tool generates the necessary mocking code with the unit test. Once this code is generated, it can be customized to represent the logic required by the test. Automated detection of dependencies and subsequent mocking code creation greatly reduces the amount of manual, and potentially error-prone, coding required to mock objects.

Test Suite Maintenance Reduced Through Automation

Maintenance of test suites overlaps much of the work required for test creation, such as creating new tests, modifying tests to properly cover the underlying logic, mocking dependencies, and executing tests. Getting assistance from Parasoft Jtest’s Unit Test Assistant during test maintenance is just as valuable as during creation, as it provides runtime analysis results collected during test execution. For example, a new dependency in an object under test is detected at runtime and the tool prompts the developer on how to deal with it.

Equally critical during this phase is ensuring that the assertions are still valid. The Parasoft Jtest Unit Test Assistant provides recommendations that can detect changes in the code and update assertions to reflect new business logic.

Maximizing the Benefit of Existing Tools

Java developers already unit testing are likely using JUnit and possibly a mocking framework for their project, such as Mockito. Test automation tools need to leverage these existing tools, since replacing an existing investment in unit testing would eliminate any cost and time benefit to be had by using an automated tool. Parasoft Jtest’s Unit Test Assistant integrates seamlessly with these existing tools, which is critical.

Conclusion

Unit testing has clear benefits. Although most development teams realize this, many are stymied by the effort of creating and maintaining tests. Using automated unit testing technologies like those in Parasoft Jtest will help users knock down these roadblocks, automating the mundane aspects of unit testing, including creation, isolation, mocking, and maintenance. To accelerate adoption, Parasoft Jtest leverages the development team’s existing investment in test and mocking frameworks and gives back time to the developer while bringing quality back to the product.

Make unit testing easier and faster with AI-enabled Parasoft Jtest.