Selecting the right test automation tools is crucial for the success of your automation efforts. The effectiveness of your test automation strategy largely depends on choosing tools that align with your project’s needs, your team’s expertise, and your budget constraints. A well-chosen tool can enhance productivity, ensure comprehensive coverage, and integrate seamlessly into your existing workflows. Tool Categories Functional Testing Tools Functional testing tools are used to validate that the software behaves as expected under various conditions. Popular tools include; Selenium Selenium is a widely-used open-source tool for automating web applications. It supports multiple programming languages and browsers, making it highly versatile. Cypress Cypress is a newer tool known for its fast execution and ability to provide real-time browser interaction. It is designed specifically for JavaScript applications and provides a more modern approach to end-to-end testing. Playwright Playwright is a cross-browser automation library that supports modern web applications. It offers powerful features for testing across different browsers and devices, and is known for its reliability and speed. API Testing Tools API testing tools are essential for verifying that APIs function correctly and meet performance standards. Key tools include; Postman Postman is a popular tool for manual API testing and automation. It provides an intuitive interface for creating and running API requests and tests. RestAssured RestAssured is a Java library for testing RESTful APIs. It offers a fluent API for writing and executing tests and integrates well with other testing frameworks. PactumJS PactumJS is an open-source tool designed for contract testing of APIs. It supports testing REST and GraphQL APIs and is known for its ease of use and flexibility. Performance Testing Tools Performance testing tools are used to assess how the application performs under various load conditions. Common tools include; JMeter Apache JMeter is a powerful tool for load testing and performance measurement. It supports a wide range of protocols and offers extensive reporting capabilities. LoadRunner LoadRunner is a comprehensive performance testing tool that supports various types of performance tests, including load, stress, and endurance testing. Gatling Gatling is a modern performance testing tool known for its high performance and ease of use. It uses a Scala-based DSL for creating complex load tests. Criteria for Selection Ease of Use Consider how user-friendly the tool is. An intuitive interface and ease of setup can significantly impact productivity and reduce the learning curve for your team. Integration Assess whether the tool integrates well with your Continuous Integration/Continuous Deployment (CI/CD) pipeline. Seamless integration ensures that automated tests are executed efficiently as part of your development process. Community Support Evaluate the level of community or vendor support available. A strong support network can be crucial for troubleshooting issues and obtaining updates or enhancements. Scalability Determine if the tool can handle the complexity and size of your project. Scalability ensures that the tool can accommodate growing testing needs and adapt to evolving project requirements. Comparative Analysis Create a matrix to compare popular tools based on features, pricing, and usability. This matrix should provide a clear overview of how each tool stacks up against the criteria outlined above, helping you make an informed decision based on your specific needs and constraints.
In the fast-paced world of software development, achieving both speed and quality is essential. DevOps, which combines development and operations, is a powerful approach to meet these goals. At the heart of DevOps are Continuous Integration (CI) and Continuous Delivery (CD), processes that streamline the development, testing, and deployment of software. Let’s dive deeper into some best practices for making the most out of CI/CD. What is CI/CD? Before we jump into best practices, let’s clarify what CI/CD is: Continuous Integration (CI): This involves developers frequently merging their code changes into a shared repository where automated builds and tests run. This helps catch and fix problems early. Continuous Delivery (CD): After the CI stage, code changes are automatically prepared for a release to production, ensuring that the software can be deployed at any time. Best Practices for CI/CD Keep a Single Source Repository Maintaining a centralized repository like Git is crucial. It ensures that all team members work with the same code base and makes tracking changes easier. Popular platforms include GitHub, GitLab, and Bitbucket. Having a single source of truth for your codebase helps prevent conflicts and confusion. It ensures that everyone on the team is on the same page and that all changes are tracked and documented. This setup also makes it easier to manage code reviews, integrate new features, and collaborate effectively. Automate Builds and Tests Automation is key in CI/CD. Every time code is committed, automated builds and tests should run. This ensures that the new code integrates well with the existing codebase and doesn’t introduce bugs. Automated builds and tests save time and reduce human error. By running tests automatically with every code change, you can catch bugs early when they are easier and cheaper to fix. This practice also helps maintain a high level of code quality and ensures that your application remains stable as new features are added. Commit Code Frequently Encourage developers to make small, incremental changes and commit them frequently. This practice makes it easier to identify which commit caused a problem if something goes wrong. Frequent commits lead to smaller changesets, which are easier to review and debug. This practice also reduces the risk of conflicts and makes it easier to integrate new features. By committing code regularly, developers can get quicker feedback on their changes, which helps in maintaining a steady development pace. Code Reviews and Pair Programming Before merging code into the main branch, have at least one other developer review it. Code reviews help catch potential issues and improve code quality. Pair programming, where two developers work together on the same code, can also be beneficial. Code reviews and pair programming foster collaboration and knowledge sharing. They help catch bugs and improve code quality by providing an additional set of eyes on the code. This practice also promotes best coding practices and helps junior developers learn from more experienced team members. Use a Staging Environment A staging environment that mirrors production allows you to test the software in a setting that’s as close to the real world as possible before releasing it to users. Testing in a staging environment helps catch issues that might not be apparent in a development environment. It ensures that the software works as expected under conditions that closely resemble the production environment. This practice reduces the risk of deploying faulty code to production and helps maintain a high level of user satisfaction. Monitor and Optimize Build Times Long build times can slow down development. Regularly monitor and optimize build times. This might involve improving test efficiency or upgrading infrastructure. Efficient build processes keep the development workflow smooth and fast. By minimizing build times, you reduce the feedback loop for developers, allowing them to iterate quickly and effectively. Optimizing build times also helps in maintaining the momentum of the development process and prevents bottlenecks. Implement Feature Toggles Feature toggles (or flags) allow you to deploy new features to production but keep them disabled until they’re ready for release. This enables continuous delivery without exposing unfinished features to users. Feature toggles provide flexibility in deploying new code. They allow you to merge code into the main branch without worrying about unfinished features being visible to users. This practice also makes it easier to test new features in production and roll them out gradually, reducing the risk of introducing bugs. Integrate Security Checks Security should be integrated into your CI/CD pipeline. Use tools like static code analysis and vulnerability scanners to detect security issues early in the development process. Integrating security checks into your CI/CD pipeline ensures that security is a continuous consideration, not an afterthought. Early detection of security vulnerabilities helps prevent potential breaches and ensures that your application remains secure. This practice also helps maintain compliance with security standards and regulations. Plan for Rollbacks Be prepared for failures. Implement strategies to quickly roll back to a previous stable state if a deployment causes issues. This might involve automated rollback scripts or maintaining a version history. Rollbacks provide a safety net in case something goes wrong during deployment. Having a rollback plan in place ensures that you can quickly revert to a stable state, minimizing downtime and disruption for users. This practice also builds confidence in the deployment process, knowing that you have a fallback option. Continuous Monitoring and Feedback Once the software is deployed, monitor its performance and gather user feedback. Use this information to make continuous improvements. Monitoring tools can alert you to issues in real-time, allowing for quick responses. Continuous monitoring helps ensure that your software performs well in production. By gathering user feedback and monitoring performance metrics, you can identify and address issues quickly. This practice helps maintain a high level of user satisfaction and provides valuable insights for future improvements. Adopting DevOps practices for Continuous Integration and Continuous Delivery can significantly improve the speed and quality of software development. By automating builds and tests, encouraging frequent commits, and maintaining robust testing environments, you can streamline your development process
What is Test Automation Test automation involves the use of specialized tools and scripts to execute tests automatically rather than manually. This approach is particularly effective for repetitive and time-consuming tasks, helping to improve both test coverage and efficiency. Automation allows for consistent execution of tests, rapid feedback, and more comprehensive validation of the software’s functionality. When to Automate Not all tests should be automated. Automation is most beneficial in the following scenarios, Regression Testing This involves repeatedly running the same set of tests to verify that recent changes or updates to the application have not introduced new defects. Automated regression tests help ensure that existing functionality remains intact after code changes. Performance Testing Automation is crucial for conducting load, stress, and scalability tests. These tests simulate the behavior of hundreds or thousands of users to evaluate how the application performs under various conditions. Automation enables the execution of these tests efficiently and accurately. Smoke Testing Smoke testing involves running a suite of basic tests to verify that the core functionality of the application is working as expected. Automated smoke tests help quickly identify critical issues early in the development cycle, ensuring that the build is stable for further testing. Data-Driven Testing This type of testing involves running the same test case multiple times with different sets of input data. Automation facilitates data-driven testing by enabling the execution of tests with various data inputs efficiently, which would be tedious and time-consuming to perform manually. Benefits of Automation Efficiency Automation can execute tests significantly faster than manual testing, especially for large test suites. Automated tests can be scheduled to run overnight or during off-hours, allowing for continuous testing and faster feedback. Consistency Automated tests are executed in a consistent manner every time they run. This reduces the risk of human error associated with manual testing, ensuring that the tests are performed uniformly and accurately across different test cycles. Reusability Once created, automated test scripts can be reused across multiple projects and test cycles. This reusability reduces the need for repetitive test creation and enables efficient testing of similar functionalities in different versions or applications. Challenges of Automation Initial Setup Costs Setting up an automation framework and developing the initial set of test scripts can be time-consuming and costly. This involves selecting appropriate tools, designing the test architecture, and writing the test cases, which can require significant investment in terms of both time and resources. Maintenance Automated tests require ongoing maintenance to ensure they remain effective and relevant. As the application evolves, test scripts may need to be updated to reflect changes in functionality or user interfaces. This ongoing maintenance can be resource-intensive and needs to be managed to keep the automation framework aligned with the current state of the application.
What is Shift-Left Testing Shift-Left is a testing approach that involves integrating QA activities early in the Software Development Life Cycle (SDLC), often during the requirements and design phases. The core idea is to catch defects as early as possible, which helps in reducing the cost and effort required to fix them later. By addressing potential issues at the early stages, teams can prevent them from becoming entrenched in the codebase, leading to a more efficient development process. Why Shift-Left is Crucial Traditional testing approaches, which occur after development is completed, often fall short in preventing costly defects. Issues discovered late in the development cycle can be expensive and time-consuming to fix. By shifting QA activities to earlier stages, teams can identify and resolve problems before they become embedded in the codebase. This approach not only enhances the quality of the software but also accelerates delivery times, ultimately benefiting the project’s overall success. Implementation Strategies Involving QA in Requirements Gathering To ensure that requirements are clear, testable, and aligned with business objectives, it is essential to include QA professionals in the early stages of requirements gathering. Their involvement helps in defining acceptance criteria and identifying potential issues or ambiguities in the requirements. This early involvement ensures that the requirements are feasible and that the testing process is well-prepared from the start. Continuous Integration and Testing Implementing Continuous Integration (CI) pipelines is a key strategy in the Shift-Left approach. CI pipelines automatically run tests every time code is committed, which helps in detecting and addressing issues immediately. This continuous feedback loop allows developers to catch and fix defects as soon as they are introduced, reducing the chances of defects accumulating over time. Test-Driven Development (TDD) Test-Driven Development encourages developers to write test cases before writing the code itself. This approach ensures that the code is developed to meet the test criteria from the outset. By focusing on the tests first, developers are guided by clear requirements and expectations, which leads to better-designed and more reliable code. Benefits Reduced Defects By identifying and addressing defects early in the development process, the overall defect count is reduced. This early detection leads to higher-quality software and minimizes the need for extensive bug fixes later in the project. Faster Time-to-Market Early testing and defect resolution reduce the need for major corrections and extensive testing later in the project. As a result, teams can deliver software more quickly and efficiently, improving time-to-market and meeting project deadlines more effectively. Improved Collaboration The Shift-Left approach fosters closer collaboration between developers, testers, and business analysts. By involving QA professionals from the beginning, communication and understanding among team members are enhanced, leading to more cohesive and coordinated efforts throughout the development process.
Manual Testing Overview Manual testing is the process of executing test cases as per user functionality, without the help of automation tools. It is a basic testing concept in which the tester behaves as a real end-user and is expected to run through every feature of an application. This approach provides significant usability, functional, and general user experience insights. Why Manual Testing Matters Despite the rise of automation, manual testing remains crucial for scenarios that require human intuition. This includes usability assessments, exploratory testing of newly developed features, and verification of complex user interactions where automation scripts may not yet be established. Manual testing helps in identifying issues that automated tests might overlook, particularly those involving subjective user experiences and interactions. Key Components Test Case Design Crafting effective test cases is essential for thorough manual testing. A well-designed test case should be clear, concise, and inclusive of various scenarios. It typically comprises: Test Case ID – A unique identifier assigned to each test case for easy reference and traceability. Test Description – A summary outlining what the test case aims to verify, including the functionality or feature being tested. Preconditions – Conditions that must be met before the test can be executed, ensuring the test environment is correctly set up and any necessary data or configurations are in place. Test Steps – Detailed, sequential instructions on how to execute the test, guiding the tester through the process to ensure consistency and accuracy. Expected Result – The anticipated outcome if the application behaves as intended, serving as the benchmark for determining test success. Actual Result – The outcome observed during test execution. This is documented only when there is a deviation from the expected result, indicating a potential issue or bug. Documenting the actual result helps in tracking discrepancies and guiding further debugging or development efforts. Status – A final indication of whether the test passed or failed, based on the comparison between the expected and actual results. Common statuses include Pass, Fail, and Incomplete. Defect Reporting When a discrepancy between the expected and actual results is observed, it is crucial to document the defect thoroughly. This includes: Defect ID – A unique identifier for tracking the defect. Summary – A brief description of the defect. Steps to Reproduce – Detailed steps to replicate the issue, aiding developers in understanding and fixing the problem. Severity and Priority – Assessment of the defect’s impact on the application and its urgency for resolution. Attachments – Screenshots, logs, or other evidence supporting the defect report. Test Execution and Management Effective test execution involves managing test cycles, tracking progress, and ensuring timely completion of tests. This includes: Test Execution – Running the test cases as per the defined steps, noting any deviations from the expected results. Test Cycle Management – Organizing test cases into cycles based on features, releases, or testing phases to ensure comprehensive coverage. Test Reporting – Summarizing test results, including pass/fail rates, defect metrics, and overall test coverage. Reports provide insights into the quality of the application and help in decision-making. By incorporating these components into the manual testing process, teams can ensure a thorough evaluation of the software, identify potential issues early, and enhance overall quality assurance efforts.
Author: Chathura Dhanushka Date: 15/03/2024 Introduction: The role of a Test Engineer in software development is paramount, as they are responsible for ensuring the quality and reliability of the software before it reaches the end-users. However, there often arises a question of whether a Test Engineer should feel bad about finding too many defects in a product. This article aims to delve into this question, exploring different perspectives and shedding light on the Test Engineer’s role in defect detection, as well as the broader context of software quality assurance. Understanding the Test Engineer’s Role: Test Engineers play a pivotal role in the software development lifecycle (SDLC). Their primary responsibility is to design, implement, and execute test cases to verify that the software meets the specified requirements and functions as intended. Test Engineers utilize various testing methodologies, tools, and techniques to identify defects or bugs in the software and ensure its overall quality and reliability. The Importance of Finding Defects: Detecting defects during testing is crucial for ensuring the quality and reliability of the software. Identifying defects early in the development process enables the development team to address them promptly, reducing the risk of these issues affecting the end-users. Moreover, finding defects before the software is released helps mitigate potential risks, such as customer dissatisfaction, financial losses, and damage to the organization’s reputation. Test Engineer’s Perspective: From a Test Engineer’s perspective, finding defects in the software is not a reflection of failure or incompetence. On the contrary, it demonstrates the Test Engineer’s dedication, attention to detail, and commitment to delivering high-quality software. Each defect uncovered presents an opportunity for improvement and contributes to enhancing the overall quality and reliability of the product. Impact on Team Dynamics: In some cases, Test Engineers may feel apprehensive about reporting too many defects, fearing that it may strain their relationship with other team members or reflect negatively on their performance. However, fostering a culture of collaboration, openness, and continuous improvement within the team is essential in overcoming such concerns. Test Engineers should communicate effectively with their peers, providing constructive feedback on the defects identified and working collaboratively towards resolving them. Managing Expectations: It is essential to manage expectations regarding the number of defects found during testing. While finding defects is a natural part of the testing process, it is unrealistic to expect a defect-free product. Factors such as software complexity, time constraints, resource limitations, and changing requirements contribute to the presence of defects in the software. Test Engineers should focus on identifying critical defects that pose significant risks to the product’s functionality or usability. Continuous Improvement: Test Engineers should view defect identification as an opportunity for continuous improvement. By analysing the root causes of defects and sharing lessons learned with the team, Test Engineers can contribute to the development of more robust testing strategies and processes. Additionally, leveraging automation tools, adopting best practices in testing, and embracing new technologies can help streamline the defect detection process and enhance efficiency. Conclusion: In conclusion, Test Engineers should not feel bad about finding too many defects in a product. Instead, they should embrace their role as quality assurance professionals and recognize the value they bring to the software development process. Detecting defects early, communicating effectively with team members, managing expectations, and striving for continuous improvement are essential aspects of a Test Engineer’s responsibilities. By adopting a positive mindset and focusing on delivering high-quality software, Test Engineers can contribute to the success of the project and the satisfaction of end-users.
Author: Sandeep Perera Date: Apr 18, 2024 In the ever-evolving landscape of software development, automation testing has become an indispensable component of the development lifecycle. APIs (Application Programming Interfaces) serve as the backbone of modern applications, enabling seamless communication between different software systems. Consequently, ensuring the reliability and functionality of APIs through automation testing is crucial. In this article, we’ll explore how to streamline API automation using Node.js and Cucumber, a powerful combination for creating robust and maintainable automated tests. Understanding API Automation API automation involves testing the functionality, reliability, performance, and security of APIs without manual intervention. It ensures that APIs behave as expected under various conditions and scenarios. Automated API testing offers several benefits, including: Efficiency: Automated tests can be executed quickly and repeatedly, saving time and effort compared to manual testing. Accuracy: Automation eliminates human errors, providing more reliable test results. Coverage: Automated tests can cover a wide range of use cases, including edge cases and negative scenarios, enhancing test coverage. Regression Testing: Automation enables continuous testing, facilitating early detection of regressions and preventing software defects. Introducing Node.js and Cucumber Node.js has gained widespread adoption in the development community for building fast, scalable, and efficient server-side applications. Its non-blocking, event-driven architecture makes it well-suited for handling asynchronous operations, such as network communication. Additionally, Node.js boasts a rich ecosystem of libraries and frameworks, making it an ideal choice for API automation. Cucumber is a popular open-source tool for behavior-driven development (BDD). It allows developers and testers to write test scenarios in plain text using the Gherkin syntax, which is easily understandable by non-technical stakeholders. Cucumber promotes collaboration between different roles in the development process and encourages the creation of living documentation. Setting Up the Environment Before diving into API automation with Node.js and Cucumber, you need to set up your development environment. Ensure you have Node.js and npm (Node Package Manager) installed on your machine. You can create a new Node.js project by running: Next, install the necessary dependencies: Writing API Tests with Cucumber Cucumber follows a simple, human-readable syntax for describing test scenarios. A typical Cucumber feature file consists of scenarios written in Gherkin syntax, which describe the behavior of the system under test. Here’s an example feature file for testing a RESTful API: Each step in the scenario is associated with a step definition, which is implemented in JavaScript. Let’s implement the step definitions using Axios, a popular HTTP client for Node.js: Running Tests To execute the Cucumber tests, run the following command in your terminal: Cucumber will parse the feature files and execute the associated step definitions. You’ll see the test results in the terminal output, indicating whether each scenario passed or failed. Conclusion Node.js and Cucumber offer a powerful combination for automating API tests with ease. By leveraging the expressive syntax of Cucumber and the versatility of Node.js, you can create robust, maintainable tests that accurately verify the behavior of your APIs. Whether you’re testing RESTful APIs, GraphQL endpoints, or SOAP services, Node.js and Cucumber provide a flexible and efficient solution for API automation. Start streamlining your API testing workflow today!
Author: Kokila Widyaratna Date: 02/04/2024 Photo by bluebay2014 @ Getty Images I started using Java in 2017 with version 8 where features like lambda expressions, stream API, default method, optional wrapper..etc, were getting a huge hype. I’ve been incorporating almost all those features during my day-to-day development work. Though I have been upgrading from Java 8 to 11 and then to 17, I noticed that I have been using the same old features. Of course, we are not bumping these Java versions only to get new language features but for performance and security improvements as well. But with the intention of getting to know new features in the language, I started doing a little bit of exploring and found the following talk by Venkat Subramaniam which I recommend to every Java developer. This post is inspired by this video. Feature 1 — Switch Expression (Java 14) Before going into this feature, let us look at the below code snippet and guess the console output. The console output is; Kudos if you guessed it correctly but this is a mistake which wasted a lot of my time during my early years. If you look at the first case, you can see that the break statement is missing and this is why the above code snippet is behaving in that manner. Now let’s see how the switch expression feature which was introduced as a preview in Java12 and then a standard feature in Java14, minimizes this error from happening. And the output is; console output of the example code snippet of the new switch expression feature As you can see, a break statement is not required in the switch expression. The ability to handle multiple cases is also a feature of the switch expression. An example snippet and console output is given below. Please refer to line #6. console output of the example code snippet for multiple case handling The yield keyword is also something which was introduced with this switch expression feature where we can use it to exit a switch expression by returning a value that becomes the value of the switch expression. This means we can assign the value of a switch expression to a variable. Refer to line #9 and the console output below as an example. console output of the example code snippet for the ‘yield’ keyword That is all for Part 1 of this series. In Part 2, I am planning to discuss the Java Records feature which was introduced as a preview in Java 14 and then became a standard feature in Java 16. Hope this can help! Share your thoughts too.
It is very easy to write a program that is understood by computers, but it is not easy to write a program that is easy to understand by other developers. Most codes are written once and read many times, so it is very important to write them as cleanly as possible. It might benefit others and also for future you that reading your code. Remember there are no hard rules in software development, so no point in arguing with others to prove that your opening is the best. If there is only one best to do anything why do people implement the same thing in many ways? that’s because everything has tradeoffs you only can decide what you can tolerate and what not depending on your context. With that in mind let’s start the discussion on names. There is a famous saying by Phil Karlton “There are only two hard things in Computer Science: cache invalidation and naming things.”. I’m sure that Every developer has been in a situation where they struggled to name something in their code. why? because there is no right name, it all depends on personal preferences, context, and many other factors. But we don’t need to write code to understand by whole world. We need some conventions that the working team gets comfortable with and easy to onboard a new member to the team. There is a famous article written by Tim Ottinger called Ottinger’s Rules for Variable and Class Naming which is the base of many resources you will find on naming best practices. People describe it in many ways by adding their own opinions and experiences Remember the reason we need clean maintainable code is that there is no hard end of development, requirements get added, and existing ones change due to many uncontrollable reasons. Good naming is not enough to have maintainable clean code, the structure is also very important. Here I will discuss some principles that you can use while naming and structuring code. Please don’t lie. This is the most important message I want to convey in this article. All other tips will help you to achieve this goal. One of the major benefits of writing clean code is when there is a change you should be able to do without reading lots of unrelated code pieces to understand the whole application at once. Think of a simple example, you are calling a simple getter to get a property value and then you realize there a database update operation is hidden inside that getter. Then you will never call any function without reading it. The trust is broken. There is no value in having clean code after this trust is broken. Good names and structure help you to navigate to the place you need to make the change without reading a hell lot of code. It’s fine to have long names. Comments are not the solution, developers do not read comments but they will have to read the code. Why do we need comments when we are writing code also using plain English? Comments can be useful to say why you do something in that way, not what code does. The code should speak for itself. Does not matter how well you craft the code if it’s not telling what it does. Most times lies are in function names. Especially when a side effect is executed when you run a function name that says it gives you something. The principle Command Query Separation which is also known as CQS can be used to write clean functions. Queries return some value while not changing the observable state of the system and commands make changes to the system state and do not return anything. If you can separate those two very good but reality is not that nice. So in such cases mention it in the function name. There is no better documentation than code which is the actual implementation. One obvious benefit of CQS by looking at the signature you can say whether it has side effects to not. One tip to convert queries that return something is to pass a callback function that deals with that return value which makes the query return nothing. Spend some time when you name something which will save you a lot of time in the future. Developers are very excited about new technology trends frameworks and programming languages and like to spend many hours learning them. But when they use them they are very rushed. Nobody is interested in investing time to revisit your variable and function names. If you want to do this fast you have to practice it, spend some time at the beginning and surely it will become natural to you later. The best way is to communicate your intent. Use Domain-specific words that are used in normal communication. It will reduce the gap between your normal conversation and reading a code. Don’t try to be oversmart by shortening names into nice acronyms. Over time these names become very confusing. Using long names is not a bad thing. Long functions might have a hidden class The main reason functions get long is they are reusing function-scoped variables throughout the function. That also means the function is doing many interconnected things. Isn’t that what do use classes in object-oriented languages? Even procedural languages have mechanisms to create scoped modules. So next time when you see a long function try to identify hidden classes inside it and do the extraction. The relationship between the length of name and execution scope For variables use long descriptive names when the execution scope is large. For example, class-level variables need to have full names. If you use single-letter variables in the functions you might not understand what it is and you will have to go back to the declaration to identify. If you need to go to the declaration to understand what a variable is that name is bad. But for finer scopes, it is
The Optional class was introduced in Java 8 as a key element in the stream API. Let’s first understand the problem that was caused to introduce this class. The problem The stream API was introduced in Java 8 and I assume every Java developer has used this API in their datoday development. Let’s consider a simple example. List<String> names = List.of(“vinod”, “madubashana”); names.stream() .filter(name -> name.startsWith(“a”)) .findFirst(); Now that you are designing the stream API, what should be the return value in the above case? No option other than returning null. The streams can be empty or they can become empty in the process of executing intermediate operations like filters as in the above case. But if it is designed to return null the API does not indicate that it can be null. What if you need to explicitly express that the return value can be empty through the API? The Solution The only solution is to introduce a new wrapper class and that’s what is provided natively in the Java 8 using Optional class. It is not a solution to issues related to nulls, it is intended to represent return types of our APIs to indicate that it can be empty which will require care when dealing with them. Before Java 8 we wrote our implementations but now we don’t need to do so. So basically Optional class provides an API to represent a value which also can be empty. Construction Optional is a final class and hence we can’t subclass it. The constructors are also private and static factor methods are provided to create instances. Optional.empty(); Optional.of(“vinod”); Optional.ofNullable(null); The empty method can be used to create empty optional. Never use nulls to reference an Optional instance, always use the empty method to create an empty Optional. If you return a null from a method where the return type is an Optional the whole purpose of introducing Optional is a waste. The of method can be only used to wrap a non-null reference. If a null value is passed a NullPointerException will be thrown. The ofNullable method allows to passing of null references. That does not mean that you can consume a null value from an Optional. It allows you to pass a null value but when you pass a null value it returns an empty Optional, not an Option which contains a null value. Java Serialization This is not a serializable class. So you can’t use Java serialization. This is intentionally done to support value objects which is a part of Project Valhalla, in the near future. Specialized Optionals To avoid boxing and unboxing specialized Optional classes are available like OptionalInt, OptionalLong, and OptionalDouble. Use these classes when applicable for better performance. Consume an Optional String value = optional.get(); if (optional.isPresent()) { String value1 = optional.get(); } optional.ifPresent(val -> System.out.println(val)); optional.ifPresentOrElse(System.out::println, () -> System.out.println(“no value”));String alternative = optional.orElse(“alternative”); String alternative1 = optional.orElseGet(() -> “alternative”); Optional<String> alternativeOptional = optional.or( () -> Optional.of(“alternative”));String value2 = optional.orElseThrow(); String value3 = optional.orElseThrow( () -> new RuntimeException(“exception”)); The API is self-exploratory. Let’s understand some important points regarding using these methods. The get method is the simplest way and the way you try to avoid always. This will throw NoSuchElementException when you call it for empty optional. This also suggests that this is not a solution for handling NullPointerException. It is always need to call isPresent or isEmpty methods to check the emptiness and then call the get method. But still, there are other better alternative ways available. ifPresent method simplifies this check and concisely operates using a consumer. The consumer will be only invoked if the optional is non-empty. ifPresentOrElse accepts a Runnable as a second argument which can be used to run some logic when optional is empty. orElse and orElseGet can be used to get a default value when the optional is empty. or method becomes handy when your return type is optional and you need to return a new option when the planned option to return becomes empty. Finally, it is easy to throw runtime exceptions using orElseThrow methods. Operations Another advantage of having Optional rather than returning null is the ability to run transformation operations without null checks. It provides the ability to write a fluent chain of method calls. See the below example. String name = optional.filter(name -> name.startsWith(“ab”)) .map(String::toUpperCase) .orElse(null); Hope this is also self-exploratory. Another operation available is flatmap which can be used to flatten nested Optionals similar to flatmap operation in stream API. The stream method is also available which can convert Optional into a Java stream. Then it is possible to use all the stream-related operations. Best Practices The intended and best place to use it is in the method return type. You might already experience this when using many open-source libraries. Not suitable for method arguments and constructor arguments. This is because it clutters the client code and adds unnecessary complexity. Not suitable as collection elements. This also makes it harder to read when we write the consume operations in stream pipelines. Don’t wrap collections using optional. Instead, return an empty collection object by using some helper methods like Collections.emptyList() Don’t try to be clever using tricks like wrapping objects that can be null and the use optional class API to handle the code, simple null checks will do the same thing for you which yields much more readable code. No suitable class fields. This is an overuse of optional. The optional is a wrapper which means it consumes more memory than the normal references. Overusing them can slow down your application. If the using class is serializable then we have another problem because optional are not serializable. Instead, you can return optional from the getter methods to indicate that fields can be empty. Try to avoid nested optional which makes code unreadable when consuming such optional. Don’t use identity-sensitive operations like equality check using == which can give unpredictable results. The equals method works as expected by comparing values inside optional.
