Table of Contents
- Understanding the Importance of Unit Testing in Java
- How to Write a Unit Test in Java: A Step-by-Step Guide
- Exploring JUnit Test Case Examples in Java
- Best Practices for Writing Effective Test Cases in Java
- Strategies for Managing Technical Debt and Legacy Code in Unit Testing
- Adapting to Changing Requirements: Implementing Robust and Flexible Testing Frameworks
- Workload Management and Deadline Balancing: Optimizing Your Testing Efforts
Introduction
Unit testing is a crucial practice in software development, particularly in Java, as it verifies the functionality of individual software components and ensures their alignment with expectations. By safeguarding code quality and detecting potential issues early in the development cycle, unit testing streamlines debugging and refactoring, while also serving as code documentation. In the rapidly evolving digital landscape, even previously well-functioning code must undergo relentless testing to accommodate changes in Java versions and prevent issues from manifesting in production systems. This article explores the importance of unit testing in Java, the benefits of automated unit testing, and best practices for writing effective unit tests.
Automated unit testing, which involves executing tests without manual intervention, is a powerful tool for saving time and reducing routine work in the Java development process. It functions as a safety net, catching errors and bugs before they escalate into serious problems that could lead to customer dissatisfaction, lost revenue, or even job losses. By adopting automated unit testing and leveraging tools like JUnit, TestNG, Mockito, and PowerMock, developers can ensure the quality and reliability of their software applications. This article also discusses strategies for managing technical debt and legacy code in unit testing, adapting to changing requirements by implementing robust and flexible testing frameworks, and optimizing testing efforts to balance workload and meet deadlines
1. Understanding the Importance of Unit Testing in Java
Unit testing, a cornerstone of software development, particularly in Java, is a procedure that verifies the functionality of individual software components, ensuring their operation aligns with expectations.
This pivotal practice helps safeguard code quality and detect potential issues early in the development cycle. It streamlines debugging and refactoring, while simultaneously serving as code documentation.
The essence of unit testing lies in its ability to conserve considerable time in the Java development process by confirming that the code operates as intended. In the swiftly advancing digital landscape of today, even previously well-functioning code must undergo relentless testing. For instance, consider a function parsing date strings. A change in the default date format between Java 8 and Java 11 resulted in the function failing in Java 11. Developers need to adjust their code to accommodate JDK changes and document these modifications in the interface's Javadoc.
A similar scenario involves a function that formats numbers as currency. The function's behavior changed between Java 8 and Java 11 due to differing space characters. Unit tests can preemptively reveal such issues before they manifest in production systems, hence emphasizing the role of unit tests in maintaining expected behavior, especially when updating Java versions.
The efficiency and effectiveness of the testing process can be significantly enhanced through automated unit testing.
Automated tests, also known as unit tests or integration tests, are designed to execute without manual intervention, thereby reducing routine work and saving time. They function as a safety net, catching errors and bugs before they escalate into serious problems that could lead to server downtime, customer dissatisfaction, lost revenue, or even job losses.
Much like a construction site safety net instills confidence and protection in workers, automated tests give developers a sense of security when altering source code. These tests should be written and committed to the repository, and they should be automatically executed with every code change. Developers should have the facility to run automated tests locally and have them integrated into the merge pipeline to detect any issues prior to merging into the main codebase.
The implementation of a robust safety net of automated tests is crucial for productive and safe software development. They serve as a valuable asset in a software project, providing protection against future bugs and issues. In essence, automated unit testing is akin to a safety net that allows programmers to work more productively, feel safer, and have confidence in their code. The absence of automated tests should be considered as risky as construction workers operating without a safety net, as it can lead to potential bugs or issues that may go unnoticed, resulting in negative consequences for customers and the overall project.
When considering best practices for Java unit testing, factors such as code organization, test coverage, and test design are important. Proper code organization involves structuring the test code in a way that is easy to understand and maintain. High test coverage means that the unit tests should aim to cover as much of the code as possible. Test design plays a crucial role in effective unit testing. Tests should be independent of each other, meaning that the outcome of one test should not affect the outcome of another.
Mocking frameworks, such as Mockito, can be used to isolate the code being tested from its dependencies, which helps in creating more focused and reliable tests. It is also beneficial to automate the execution of unit tests using build tools like Maven or Gradle, ensuring that the tests are run regularly and consistently throughout the development process.
Automated unit testing tools for Java, such as JUnit, TestNG, Mockito, PowerMock, and EasyMock, can be beneficial in ensuring the quality and reliability of software applications.
These tools help developers automatically test individual units of code to identify any errors or bugs. These tools simplify the process of unit testing and improve the overall efficiency of the testing process. Developers can choose the tool that best suits their requirements and integrate it into their development workflow to achieve better code quality and faster release cycles.
Code coverage tools, such as JaCoCo, Cobertura, and Emma, available for Java unit testing can help developers assess the effectiveness of their tests. These tools provide insights into which parts of the codebase are being covered by the unit tests and identify areas that may require additional testing. By measuring code coverage, developers can ensure that their tests are comprehensive and identify any gaps in test coverage.
Avoiding common mistakes in Java unit testing leads to accurate test results and enhances the effectiveness of the testing process. By avoiding these mistakes, developers can ensure that their unit tests are reliable and provide accurate feedback on the functionality of their code. By following these practices, developers can create robust and maintainable unit tests for their Java code
2. How to Write a Unit Test in Java: A Step-by-Step Guide
Unit testing in Java, a vital aspect of software development, ensures the correct operation of individual code components. The initial steps of the process involve identifying the code unit to be tested, which may be a method, a class, or a module, and defining the expected outcomes for different conditions of the identified unit.
The creation of a test case follows, wherein the chosen unit is executed with a specific set of inputs. The key task at this stage is to confirm the alignment of the actual outcome with the expected one, for which a testing framework like JUnit can be beneficial. The final stage involves running the test case and analyzing the results. If there are discrepancies between the actual and the expected outcome, it may indicate a potential bug in the code unit.
Unit testing is significant in identifying bugs early in the development process, thereby ensuring that the code meets quality standards. This process involves writing tests to verify the expected output of a method and to check if the desired function calls are made inside the method. Techniques such as mocking, commonly done using libraries like Mockito, can be used to create mock objects or stub methods for testing classes that have external dependencies.
When writing unit tests, initializing necessary parameters, creating mock objects, stubbing methods if required, and calling the method being tested with the initialized parameters are essential steps. Assertions are used to determine test pass or fail status by comparing the expected return value of a method with the actual one. It is crucial to cover different scenarios and conditions in unit tests, including branching functions, sorting arrays, and mocking external dependencies like database connections.
Method stubbing, a technique used in mocking, can be used to make a method return a specific value instead of calling the actual method. Unit tests for methods involving database queries can be done by mocking the database connection and stubbing the methods that make the queries. Test cases should cover both successful scenarios and scenarios where exceptions are thrown. Unit tests play a critical role in identifying bugs earlier in the development process and ensure the software meets quality standards.
Best practices for effective Java unit testing include proper code organization, high test coverage, and independent test design.
It is beneficial to utilize mocking frameworks like Mockito to isolate the code being tested from its dependencies. Further, automation of unit test execution using build tools like Maven or Gradle ensures that the tests are run regularly and consistently throughout the development process.
To set up a project for unit testing in an IDE like IntelliJ or Visual Studio Code, one needs to create a new project and add the necessary dependencies in the pom.xml file. Libraries like JUnit and Mockito are commonly used for unit testing in Java. For instance, the JUnit Jupiter dependency (org.junit.jupiter:junit-jupiter) is used for testing, and the Mockito dependency (org.mockito:mockito-inline) is used for mocking. These libraries aid in creating robust and comprehensive unit tests.
Moreover, writing clear and concise test cases, using descriptive test method names, using proper test data, testing both positive and negative scenarios, mocking external dependencies, using assertions to validate results, and automating test execution are among the best practices to write effective unit tests in Java. These practices provide reliable feedback on the correctness and robustness of your code
3. Exploring JUnit Test Case Examples in Java
JUnit 5, the evolution of the widely used Java testing framework, JUnit, offers a plethora of enhancements that are based on the Java 8 version of the language. It allows for the configuration of Maven and Gradle build systems, facilitating the testing process.
Tests in JUnit 5 are defined using the @Test
annotation. This annotation is applied to a method that needs to be tested. For example, consider the following JUnit test case:
java
@Test
public void testAddition() {
Calculator calculator = new Calculator();
int result = calculator.add(2, 2);
assertEquals(4, result);
}
In the above test case, an instance of the Calculator
class is created, and the add()
method is invoked with the inputs 2
and 2
. The assertEquals()
method is then used to verify if the result of the addition operation is 4
. The assertEquals()
method is used to compare two values and check if they are equal. It is commonly used in unit testing to verify that the expected and actual values are the same. This method takes two parameters: the expected value and the actual value. If the two values are equal, the test passes; otherwise, it fails.
JUnit 5 extends its support for assertions and assumptions, allowing developers to test expected outcomes and conditions more effectively. The framework offers various annotations such as @Test
, @Disabled
, @BeforeEach
, @AfterEach
, @RepeatedTest
, @Tag
, @TestFactory
, @Nested
, @BeforeAll
, @AfterAll
, and @ExtendWith
for test configuration and execution.
One of the key features of JUnit 5 is the ability to create dynamic and parameterized tests. This facilitates more flexible testing scenarios. Additionally, the @TempDir
annotation helps in creating temporary files and paths required for testing.
JUnit 5 also supports the creation of test suites and the grouping of tests, which simplifies managing and executing related test cases. It offers features for testing exceptions and conditional enablement of tests, adding an extra layer of robustness to your testing suite.
The framework allows for the creation of nested tests, which can be used to group related tests for easier management and display. Another useful feature is the ability to create test reports for Maven and Gradle projects, which can be used to track test results over time.
The JUnit 5 GitHub repository is a comprehensive reference, containing a plethora of tests that can be referred to when writing your own.
In essence, JUnit 5 is a powerful tool that can be utilized to ensure the correctness of your code, making it an indispensable part of Java application development. Its rich set of features and support for modern Java versions make it a go-to choice for many developers when it comes to unit testing
4. Best Practices for Writing Effective Test Cases in Java
Rewriting Java unit tests effectively demands adherence to several best practices. A primary rule is the independence of each test case. This independence ensures that the outcome of a test case doesn't influence another, allowing each test case to stand alone without dependency on the state of others.
An additional principle is simplicity and focus. Each test case should concentrate on testing a single component or functionality at a time. This approach provides clarity and assists in identifying and rectifying bugs if the test fails.
Descriptive names for test methods constitute another good practice. A well-named test method clearly indicates the functionality or component the test is validating. This practice simplifies the process of test maintenance and makes it easier for other developers to understand the purpose of each test.
Assertions are a potent tool in unit testing, used to confirm that the system behaves as expected under different conditions. Each test should include suitable and sufficient assertions to validate the behavior being tested.
Handling exceptions in test cases is a crucial part of writing robust unit tests. When a method is expected to throw an exception under certain circumstances, a test case should validate this behavior. This practice ensures that the system gracefully handles errors and behaves as expected under exceptional circumstances.
Testing exceptions, however, presents a unique set of challenges. The code paths that can potentially throw exceptions are numerous, and each one needs to be tested. The key to effective exception testing is to ensure that the work is either completed or rolled back, and that resources such as network connections or file handles are freed.
There are two primary ways to cause the code inside a try
block to throw exceptions: creating conditions that cause the actual code to throw the desired exception, or using substitute code (a test double) that throws an exception. The latter approach is useful for unit testing as it allows for thorough testing of exception handling without the need to set up complex conditions.
Three common strategies can create a test double that throws exceptions: using lambdas/closures, creating fake objects that mimic or subclass the throwing object, or using mock objects from a mock object library. These strategies allow comprehensive testing of exception handling in unit tests.
java
MyClass myObjectMock = Mockito.mock(MyClass.class);
Mockito.doThrow(new MyException()).when(myObjectMock).myMethod();
try {
myObjectMock.myMethod();
Assert.fail("Expected MyException to be thrown");
} catch (MyException e) {
}
The process of writing effective unit tests in Java requires careful planning and adherence to best practices like ensuring test case independence, maintaining simplicity and focus, using descriptive test method names, making effective use of assertions, and appropriately handling exceptions. By following these practices, developers can create robust and reliable unit tests that contribute to the overall quality and reliability of the software
5. Strategies for Managing Technical Debt and Legacy Code in Unit Testing
Legacy code and technical debt can present significant challenges in unit testing efforts. A well-structured approach to these issues is vital, which could involve gradually refactoring legacy code to enhance its testability. This refactoring may involve simplifying complex methods into smaller, more comprehensible units and decoupling dependencies.
An episode during a "sustainability week" at a previous organization underscored the importance of having clear goals in refactoring. The week was dedicated to refining the codebase and addressing technical debt. However, a problem arose with cleaning the database for tests, causing the test suite to fail. This incident highlighted the complexities of decoupling tests and cleaning up tests without disrupting the entire codebase.
Refactoring legacy code requires a structured approach. An important step in this process is the addition of automated tests to establish a safety net for changes. The test pyramid concept, which advocates for a large number of unit tests, a reasonable amount of integration tests, and a few UI or end-to-end tests, is a beneficial strategy. Dependency injection can further aid in decoupling tightly coupled code, easing the process of writing unit tests.
Several refactoring techniques can be applied to improve the testability of the codebase. These include extracting methods, introducing interfaces, dependency injection, applying the Single Responsibility Principle, and writing testable code. For instance, the extraction of smaller, independent methods from complex methods can isolate specific functionality and facilitate individual testing of each method. This not only simplifies unit testing of each functionality but also enhances code readability and maintainability.
Decoupling dependencies in legacy code for unit testing can be achieved using techniques such as dependency injection and mocking. Dependency injection separates the creation and management of dependencies from the code that uses them, allowing for the replacement of dependencies with test doubles during unit testing. Mocking frameworks can create these mock objects, simulating the real dependencies' behavior without the actual implementation. This approach enables the isolation of the code under test and the writing of comprehensive unit tests unhindered by dependencies.
Before refactoring, writing regression tests for legacy code is crucial to ensure that the refactoring process does not introduce new bugs or regressions into the codebase. Having a suite of regression tests covering the existing functionality of the legacy code provides developers the confidence that the refactored code behaves as expected and does not break any existing functionality.
In managing technical debt in unit testing, it is vital to follow effective unit testing practices. These include writing clear and concise test cases, maintaining good code coverage, regularly refactoring test code, and continuously integrating and running tests. By adopting these practices, developers can minimize technical debt in unit testing and ensure the quality and reliability of their code.
When working with legacy code that has high technical debt, several strategies can be employed to effectively test the codebase. These include identifying and isolating the critical parts of the code that are most likely to contain bugs, using automated testing tools and frameworks, establishing a comprehensive test suite, and fostering a strong feedback loop between developers and testers.
In conclusion, improving legacy code is a journey requiring time, effort, and a step-by-step approach. Balancing the need for improving code quality with the need for delivering value quickly is crucial.
Therefore, having a clear goal, timeboxing, and not being afraid to start over if necessary, are some general rules for refactoring legacy code
6. Adapting to Changing Requirements: Implementing Robust and Flexible Testing Frameworks
Adapting to shifting software requirements necessitates a testing process that is both robust and malleable. It's imperative to have a flexible testing framework that can readily accommodate changes. This flexibility encompasses the seamless integration or removal of test cases and the provision for various testing methods, such as unit, integration, and functional testing.
The landscape of software development undergoes a paradigm shift approximately every decade, introducing new challenges in delivering maintainable software. While new technologies can resolve issues for users and developers, they also present difficulties in creating maintainable software. The documentation for these new technologies primarily emphasizes their usability, often leaving maintainability practices less defined. Consequently, applying traditional software design principles to these contemporary technologies can be a daunting task, as they may not translate directly.
One enduring software design principle is the incremental or evolutionary design, which postulates that the best design is one that can adapt to continuous changes and new requirements. This principle aids in avoiding the pitfalls of both under-designing and over-designing. Techniques like functional and object-oriented paradigms, test-driven development, and system decoupling can enhance evolutionary design.
The emergence of new technologies challenges the conventional understanding of fundamental software design practices. As a result, what was once considered essential may now be seen as just one of many tools. Developers, therefore, bear the responsibility of guiding projects and striking a balance between under-designing and over-designing. Acquiring proficiency in evolutionary design requires specific knowledge and the unlearning of certain concepts. Classic literature on the subject, such as "Refactoring" by Martin Fowler, can offer invaluable insights into understanding and executing evolutionary design principles.
Incorporating a widely supported testing framework like JUnit, which boasts a comprehensive set of features, can significantly enhance the flexibility and adaptability of the testing process. Agile teams should continually learn and adapt their product and processes. The holistic testing model, introduced by Janet Gregory and Lisa Crispin on July 1, 2022, utilizes information from production usage to drive changes and solve customer problems. Visual collaboration tools, such as root cause analysis tools and fishbone diagrams, can be instrumental in exploring and addressing issues. Small and frugal experiments can be employed to tackle challenges and make progress towards goals. Concrete measurements should be utilized to track progress and determine the success of experiments. Usability testing can be integrated into the release process to encourage customers to try new features. Collaboration among team members, including testers, programmers, designers, product owners, and operations specialists, is crucial for learning and improvement. The Agile Testing Fellowship offers courses and training for agile testers. Applying evolutionary design principles can bring benefits and make code more maintainable across different technologies and ecosystems. Big Nerd Ranch offers resources and training on evolutionary design, and developers can explore the topic further to improve their software design skills."
In implementing a robust and flexible testing framework for evolving software requirements, it's beneficial to follow best practices for unit testing. This can include writing modular and independent test cases, using mocking frameworks to isolate dependencies, and employing continuous integration and automated testing. By breaking down the software into smaller, testable units, each component can be ensured to function as expected and can be easily modified or replaced as requirements change. Using mocking frameworks allows for the simulation of the behavior of external dependencies, making the tests more reliable and reducing the impact of changes in those dependencies.
Continuous integration and automated testing are also crucial for maintaining a flexible testing framework. Automatically running tests whenever changes are made to the codebase can quickly identify any issues and ensure that all tests pass before deploying the software. This not only saves time but also helps catch any regression bugs that may have been introduced due to changing requirements.
In a flexible testing framework, test case addition, modification, and removal can be performed to enhance the testing process. These actions allow for the dynamic adaptation of the test suite based on changing requirements or application updates. Test case addition involves creating new test cases to cover additional functionality or scenarios. Test case modification entails updating existing test cases to reflect changes in system behavior or test requirements. Test case removal involves removing obsolete or redundant test cases that are no longer relevant. These actions ensure that the testing framework remains agile and adaptable to the evolving needs of the software being tested.
A robust testing framework supports various types of testing to ensure the quality and reliability of software. These types of testing include unit testing, integration testing, system testing, and acceptance testing. Unit testing focuses on testing individual units or components of the software to ensure they function correctly in isolation. It helps identify bugs or issues within smaller sections of the code. Integration testing verifies the correct behavior of multiple components when they are integrated and interact with each other. It aims to identify issues that may arise due to the integration of different modules or systems. System testing evaluates the entire system as a whole to ensure that all components work together correctly and meet the specified requirements. It involves testing the complete software system, including its interfaces, interactions, and dependencies. Acceptance testing is performed to verify that the software meets the user's requirements and performs as expected. It involves testing the software from a user's perspective and validating its functionality against the specified acceptance criteria. A robust testing framework should support all these types of testing to ensure comprehensive test coverage and facilitate the identification of issues at different levels of the software development lifecycle.
There have been successful implementations of flexible testing frameworks in various scenarios. These case studies have demonstrated the benefits and advantages of using such frameworks. By adopting a flexible testing framework, organizations have been able to improve their testing processes, enhance test coverage, and achieve better overall product quality. These frameworks have provided the ability to easily adapt to changing testing requirements, support different testing methodologies, and integrate with existing tools and systems. The successful implementation of these flexible testing frameworks has resulted in increased efficiency, reduced time-to-market, and improved customer satisfaction.
Finally, a testing framework can effectively manage changing software requirements by following these guidelines: Use a modular approach, implement automation, maintain good documentation, regularly update test cases, prioritize test coverage, and collaborate with stakeholders. By following these guidelines, a testing framework can effectively manage changing software requirements and ensure the quality of the software product
7. Workload Management and Deadline Balancing: Optimizing Your Testing Efforts
Balancing the workload and adhering to deadlines in software development can be a complex task. However, through the optimization of the testing process, this challenge can be managed effectively. The key lies in aligning testing efforts with the potential risk and impact of bugs on the system. This can be achieved by prioritizing testing based on risk and impact.
Prioritizing testing involves conducting a risk analysis or risk assessment, identifying potential risks and their potential impacts, and assigning a risk level or priority to each. The risks can be categorized based on factors such as likelihood, severity, and potential business impact. The higher the risk level, the higher the priority for testing. The functionalities or components that are crucial for the software's core functionality or have a significant impact on user experience should be given higher priority for testing.
The automation of the testing process is another crucial factor in managing workload and deadlines. Automated testing tools and frameworks allow developers to write scripts that can automatically execute test cases, validate results, and generate reports. This not only saves time and resources but also improves the overall quality of the software being developed. It also allows for regression testing, which involves re-running tests on previously developed software to ensure that new changes or updates have not introduced any new issues.
Continuous integration tools serve as a robust solution for this issue. These tools enable developers to regularly integrate their code changes into a shared repository, which is then automatically built and tested. This ensures that any issues or conflicts are identified early on, leading to more stable and reliable software. It is important to have a comprehensive suite of automated tests in place and to configure the continuous integration tool to run these tests automatically whenever code changes are pushed to the repository.
As the company matures, the degree and nature of optimization required evolve. Early stages or the pre-launch phase should emphasize high friction, high bandwidth, non-scalable qualitative optimization. This involves gathering feedback by interacting with actual users rather than just focusing on quantitative optimization. As the company grows, it becomes increasingly important to focus on optimization through quantitative analysis and testing of various marketing and sales strategies.
In software testing, optimizing test feedback time in Xcode, a popular development tool, can be a good example. Starting with a new project to measure the initial test time and then implementing improvements to reduce it is a recommended approach. Several techniques can be employed to optimize the test feedback time, including the removal of UI tests, disabling parallel tests, and turning off the debugger. Existing projects can also be checked for custom build scripts and disabling dsym generation for debug builds to further enhance test feedback time.
In essence, workload management and deadline balancing in software development can be effectively handled by optimizing the testing process. Prioritizing testing based on risk and impact, automating the testing process, and using continuous integration tools are some of the strategies that can be employed. As the company grows, it becomes increasingly important to focus on optimization through quantitative analysis and testing of various strategies. The ultimate goal is continuous improvement of the company's efforts in marketing, sales, and particularly in the area of software testing
Conclusion
In conclusion, unit testing is a crucial practice in Java software development that ensures the functionality and quality of individual software components. It serves as a safety net, catching errors and bugs before they escalate into serious problems that could impact customer satisfaction or revenue. Automated unit testing, with tools like JUnit and Mockito, is particularly valuable in saving time and reducing routine work. It provides developers with confidence in their code and allows for efficient debugging and refactoring. Unit testing also plays a vital role in adapting to changing requirements by accommodating updates in Java versions and preventing issues from manifesting in production systems.
The importance of unit testing extends beyond code functionality. It also serves as code documentation, making it easier for developers to understand and maintain the codebase. By following best practices such as proper code organization, high test coverage, and independent test design, developers can write effective unit tests that enhance the reliability and maintainability of their software applications. The implementation of robust and flexible testing frameworks, along with strategies for managing technical debt and legacy code, further contribute to the overall quality of the software development process. To boost your productivity with Machinet, experience the power of AI-assisted coding and automated unit test generation here.
Conclusion
In conclusion, unit testing is a fundamental practice in Java software development that verifies the functionality of individual components and ensures code quality. Automated unit testing saves time by catching errors early in the development cycle and reducing routine work. It serves as a safety net, preventing serious problems that could impact customer satisfaction or revenue. Unit testing is essential for adapting to changing requirements by accommodating updates in Java versions and preventing issues from appearing in production systems.
To write effective unit tests, developers should follow best practices such as proper code organization, high test coverage, and independent test design. They can leverage tools like JUnit and Mockito to automate the execution of tests and ensure the reliability of their software applications. Managing technical debt and legacy code requires a structured approach that involves gradually refactoring the codebase to enhance its testability.
By implementing robust and flexible testing frameworks, developers can optimize their testing efforts while balancing workload management and meeting deadlines. Prioritizing testing based on risk levels helps allocate resources effectively, while automation streamlines the testing process. Overall, unit testing plays a critical role in maintaining code quality, improving productivity, and ensuring the success of software projects.
Boost your productivity with Machinet. Experience the power of AI-assisted coding and automated unit test generation here
AI agent for developers
Boost your productivity with Mate. Easily connect your project, generate code, and debug smarter - all powered by AI.
Do you want to solve problems like this faster? Download Mate for free now.