Also known as the build stage of the SDLC, coding focuses on the writing and programming of a system. The Zones in this category take a hands-on approach to equip developers with the knowledge about frameworks, tools, and languages that they can tailor to their own build needs.
A framework is a collection of code that is leveraged in the development process by providing ready-made components. Through the use of frameworks, architectural patterns and structures are created, which help speed up the development process. This Zone contains helpful resources for developers to learn about and further explore popular frameworks such as the Spring framework, Drupal, Angular, Eclipse, and more.
Java is an object-oriented programming language that allows engineers to produce software for multiple platforms. Our resources in this Zone are designed to help engineers with Java program development, Java SDKs, compilers, interpreters, documentation generators, and other tools used to produce a complete application.
JavaScript (JS) is an object-oriented programming language that allows engineers to produce and implement complex features within web browsers. JavaScript is popular because of its versatility and is preferred as the primary choice unless a specific function is needed. In this Zone, we provide resources that cover popular JS frameworks, server applications, supported data types, and other useful topics for a front-end engineer.
Programming languages allow us to communicate with computers, and they operate like sets of instructions. There are numerous types of languages, including procedural, functional, object-oriented, and more. Whether you’re looking to learn a new language or trying to find some tips or tricks, the resources in the Languages Zone will give you all the information you need and more.
Development and programming tools are used to build frameworks, and they can be used for creating, debugging, and maintaining programs — and much more. The resources in this Zone cover topics such as compilers, database management systems, code editors, and other software tools and can help ensure engineers are writing clean code.
Database Systems
Every modern application and organization collects data. With that, there is a constant demand for database systems to expand, scale, and take on more responsibilities. Database architectures have become more complex, and as a result, there are more implementation choices. An effective database management system allows for quick access to database queries, and an organization can efficiently make informed decisions. So how does one effectively scale a database system and not sacrifice its quality?Our Database Systems Trend Report offers answers to this question by providing industry insights into database management selection and evaluation criteria. It also explores database management patterns for microservices, relational database migration strategies, time series compression algorithms and their applications, advice for the best data governing practices, and more. The goal of this report is to set up organizations for scaling success.
Java developers have often envied JavaScript for its ease of parsing JSON. Although Java offers more robustness, it tends to involve more work and boilerplate code. Thanks to the Manifold project, Java now has the potential to outshine JavaScript in parsing and processing JSON files. Manifold is a revolutionary set of language extensions for Java that completely changes the way we handle JSON (and much more). Getting Started With Manifold The code for this tutorial can be found on my GitHub page. Manifold is relatively young but already vast in its capabilities. You can learn more about the project on their website and Slack channel. To begin, you'll need to install the Manifold plugin, which is currently only available for JetBrains IDEs. The project supports LTS releases of Java, including the latest JDK 19. We can install the plugin from IntelliJ/IDEAs settings UI by navigating to the marketplace and searching for Manifold. The plugin makes sure the IDE doesn’t collide with the work done by the Maven/Gradle plugin. Manifold consists of multiple smaller projects, each offering a custom language extension. Today, we'll discuss one such extension, but there's much more to explore. Setting Up a Maven Project To demonstrate Manifold, we'll use a simple Maven project (it also works with Gradle). We first need to paste the current Manifold version from their website and add the necessary dependencies. The main dependency for JSON is the manifold-json-rt dependency. Other dependencies can be added for YAML, XML, and CSV support. We need to add this to the pom.xml file in the project. I'm aware of the irony where the boilerplate reduction for JSON starts with a great deal of configuration in the Maven build script. But this is configuration, not "actual code" and it's mostly copy and paste. Notice that if you want to reduce this code the Gradle equivalent code is terse by comparison. This line needs to go into the properties section: <manifold.version>2023.1.5</manifold.version> The dependencies we use are these: <dependencies> <dependency> <groupId>systems.manifold</groupId> <artifactId>manifold-json-rt</artifactId> <version>${manifold.version}</version> </dependency> The compilation plugin is the boilerplate that weaves Manifold into the bytecode and makes it seamless for us. It’s the last part of the pom setup: <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.0</version> <configuration> <source>19</source> <target>19</target> <encoding>UTF-8</encoding> <compilerArgs> <!-- Configure manifold plugin--> <arg>-Xplugin:Manifold</arg> </compilerArgs> <!-- Add the processor path for the plugin --> <annotationProcessorPaths> <path> <groupId>systems.manifold</groupId> <artifactId>manifold-json</artifactId> <version>${manifold.version}</version> </path> </annotationProcessorPaths> </configuration> </plugin> </plugins> </build> With the setup complete, let's dive into the code. Parsing JSON With Manifold We place a sample JSON file in the project directory under the resources hierarchy. I placed this file under src/main/resources/com/debugagent/json/Test.json: { "firstName": "Shai", "surname": "Almog", "website": "https://debugagent.com/", "active": true, "details":[ {"key": "value"} ] } In the main class, we refresh the Maven project, and you'll notice a new Test class appears. This class is dynamically created by Manifold based on the JSON file. If you change the JSON and refresh Maven, everything updates seamlessly. It’s important to understand that Manifold isn’t a code generator. It compiles the JSON we just wrote into bytecode. The Test class comes with several built-in capabilities, such as a type-safe builder API that lets you construct JSON objects using builder methods. You can also generate nested objects and convert the JSON to a string by using the write() and toJson() methods. It means we can now write: Test test = Test.builder().withFirstName("Someone") .withSurname("Surname") .withActive(true) .withDetails(List.of( Test.details.detailsItem.builder(). withKey("Value 1").build() )) .build(); Which will printout the following JSON: { "firstName": "Someone", "surname": "Surname", "active": true, "details": [ { "key": "Value 1" } ] } We can similarly read a JSON file using code such as this: Test readObject = Test.load().fromJson(""" { "firstName": "Someone", "surname": "Surname", "active": true, "details": [ { "key": "Value 1" } ] } """); Note the use of Java 15 TextBlock syntax for writing a long string. The load() method returns an object that includes various APIs for reading the JSON. In this case, it is read from a String but there are APIs for reading it from a URL, file, etc. Manifold supports various formats, including CSV, XML, and YAML, allowing you to generate and parse any of these formats without writing any boilerplate code or sacrificing type safety. In order to add that support we will need to add additional dependencies to the pom.xml file: <dependency> <groupId>systems.manifold</groupId> <artifactId>manifold-csv-rt</artifactId> <version>${manifold.version}</version> </dependency> <dependency> <groupId>systems.manifold</groupId> <artifactId>manifold-xml-rt</artifactId> <version>${manifold.version}</version> </dependency> <dependency> <groupId>systems.manifold</groupId> <artifactId>manifold-yaml-rt</artifactId> <version>${manifold.version}</version> </dependency> With these additional dependencies, this code will print out the same data as the JSON file. With test.write().toCsv() the output would be: "firstName","surname","active","details" "Someone","Surname","true","[manifold.json.rt.api.DataBindings@71070b9c]" Notice that the Comma Separated Values (CSV) output doesn’t include hierarchy information. That’s a limitation of the CSV format and not the fault of Manifold. With test.write().toXml() the output is familiar and surprisingly concise: <root_object firstName="Someone" surname="Surname" active="true"> <details key="Value 1"/> </root_object> With test.write().toYaml() we again get a familiar printout: firstName: Someone surname: Surname active: true details: - key: Value 1 Working With JSON Schema Manifold also works seamlessly with JSON schema, allowing you to enforce strict rules and constraints. This is particularly useful when working with dates and enums. Manifold seamlessly creates/updates byte code that adheres to the schema, making it much easier to work with complex JSON data. This schema is copied and pasted from the Manifold GitHub project: { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "http://example.com/schemas/User.json", "type": "object", "definitions": { "Gender": { "type": "string", "enum": ["male", "female"] } }, "properties": { "name": { "type": "string", "description": "User's full name.", "maxLength": 80 }, "email": { "description": "User's email.", "type": "string", "format": "email" }, "date_of_birth": { "type": "string", "description": "Date of uses birth in the one and only date standard: ISO 8601.", "format": "date" }, "gender": { "$ref" : "#/definitions/Gender" } }, "required": ["name", "email"] } It’s a relatively simple schema, but I’d like to turn your attention to several things here. It defines name and email as required. This is why when we try to create a User object using a builder in Manifold, the build() method requires both parameters: User.builder("Name", "email@domain.com") That is just the start. The schema includes a date. Dates are a painful prospect in JSON, the standardization is poor and fraught with issues. The schema also includes a gender field which is effectively an enum. This is all converted to type-safe semantics using common Java classes such as LocalDate: User u = User.builder("Name", "email@domain.com") .withDate_of_birth(LocalDate.of(1999, 10, 11)) .withGender(User.Gender.male) .build(); That can be made even shorter with static imports but the gist of the idea is clear. JSON is effectively native to Java in Manifold. Video The Tip of The Iceberg Manifold is a powerful and exciting project. It revolutionizes JSON parsing in Java but that’s just one tiny portion of what it can do! We've only scratched the surface of its capabilities in this post. In the next article, we'll dive deeper into Manifold and explore some additional unexpected features. Please share your experience and thoughts about Manifold in the comments section. If you have any questions, don't hesitate to ask.
Last week, I decided to see the capabilities of OpenAI's image generation. However, I noticed that one has to pay to use the web interface, while the API was free, even though rate-limited. Dall.E offers Node.js and Python samples, but I wanted to keep learning Rust. So far, I've created a REST API. In this post, I want to describe how you can create a Web app with server-side rendering. The Context Tokio is a runtime for asynchronous programming for Rust; Axum is a web framework that leverages the former. I already used Axum for the previous REST API, so I decided to continue. A server-side rendering Web app is similar to a REST API. The only difference is that the former returns HTML pages, and the latter JSON payloads. From an architectural point of view, there's no difference; from a development one, however, it plays a huge role. There's no visual requirement in JSON, so ordering is not an issue. You get a struct; you serialize it, and you are done. You can even do it manually; it's no big deal - though a bit boring. On the other hand, HTML requires a precise ordering of the tags: if you create it manually, maintenance is going to be a nightmare. We invented templating to generate order-sensitive code with code. While templating is probably age-old, PHP was the language to popularize it. One writes regular HTML and, when necessary, adds the snippets that need to be dynamically interpreted. In the JVM world, I used JSPs and Apache Velocity, the latter, to generate RTF documents. Templating in Axum As I mentioned above, I want to continue using Axum. Axum doesn't offer any templating solution out-of-the-box, but it allows integrating any solution through its API. Here is a small sample of templating libraries that I found for Rust: handlebars-rust, based on Handlebars liquid, based on Liquid Tera, based on Jinja, as the next two askama MiniJinja etc. As a developer, however, I'm lazy by essence, and I wanted something integrated with Axum out of the box. A quick Google search lead me to axum-template, which seems pretty new but very dynamic. The library is an abstraction over handlebars, askama, and minijinja. You can use the API and change implementation whenever you want. axum-template in Short Setting up axum-template is relatively straightforward. First, we add the dependency to Cargo: Shell cargo add axum-template Then, we create an engine depending on the underlying implementation and configure Axum to use it. Here, I'm using Jinja: Rust type AppEngine = Engine<Environment<'static>>; //1 #[derive(Clone, FromRef)] struct AppState { //2 engine: AppEngine, } #[tokio::main] async fn main() { let mut jinja = Environment::new(); //3 jinja.set_source(Source::from_path("templates")); //4 let app = Router::new() .route("/", get(home)) .with_state(AppState { //5 engine: Engine::from(jinja), }); } Create a type alias. Create a dedicated structure to hold the engine state. Create a Jinja-specific environment. Configure the folder to read templates from. The path is relative to the location where you start the binary; it shouldn't be part of the src folder. I spent a nontrivial amount of time to realize it. Configure Axum to use the engine. Here are the base items: Engine is a facade over the templating library Templates are stored in a hashtable-like structure. With the MiniJinja implementation, according to the configuration above, Key is simply the filename, e.g., home.html The final S parameter has no requirement. The library will read its attributes and use them to fill the template. I won't go into the details of the template itself, as the documentation is quite good. The impl Return It has nothing to do with templating, but this mini-project allowed me to ponder the impl return type. In my previous REST project, I noticed that Axum handler functions return impl, but I didn't think about it. It's indeed pretty simple: If your function returns a type that implements MyTrait, you can write its return type as -> impl MyTrait. This can help simplify your type signatures quite a lot! - Rust By Example However, it has interesting consequences. If you return a single type, it works like a charm. However, if you return more than one, you either need a common trait across all returned types or to be explicit about it. Here's the original sample: Rust async fn call(engine: AppEngine, Form(state): Form<InitialPageState>) -> impl IntoResponse { RenderHtml(Key("home.html".to_owned()), engine, state) } If the page state needs to differentiate between success and error, we must create two dedicated structures. Rust async fn call(engine: AppEngine, Form(state): Form<InitialPageState>) -> Response { //1 let page_state = PageState::from(state); if page_state.either.is_left() { RenderHtml(Key("home.html".to_owned()), engine, page_state.either.left().unwrap()).into_response() //2 } else { RenderHtml(Key("home.html".to_owned()), engine, page_state.either.right().unwrap()).into_response() //2 } } Cannot use impl IntoResponse; need to use the explicit Response type Explicit transform the return value to Response Using the Application You can build from the source or run the Docker image, available at DockerHub. The only requirement is to provide an OpenAI authentication token via an environment variable: Shell docker run -it --rm -p 3000:3000 -e OPENAI_TOKEN=... nfrankel/rust-dalle:0.1.0 Enjoy! Conclusion This small project allowed me to discover another side of Rust: HTML templating with Axum. It's not the usual use case for Rust, but it's part of it anyway. On the Dall.E side, I was not particularly impressed with the capabilities. Perhaps I didn't manage to describe the results in the right way. I'll need to up my prompt engineering skills. In any case, I'm happy that I developed the interface, if only for fun. The complete source code for this post can be found on GitHub. To Go Further: axum-template Image generation API
Cypress is a popular end-to-end testing framework used for web applications. It is an open-source JavaScript testing tool designed to make testing web applications fast, easy, and reliable. Cypress allows developers to write automated tests that simulate user interactions and verify the behavior of the applications. We can use Cypress with JavaScript or TypeScript for development, but JavaScript is the primary language used with Cypress. Cypress is built on top of JavaScript and uses many of the same concepts of modern web development, such as using a browser-based environment to run tests and interacting with web elements using CSS Selectors. When writing tests in Cypress, you can use JavaScript to create test cases that simulate user interactions and validate the behavior of your application. Cypress also provides a powerful set of built-in commands and assertions to help you write tests quickly and easily. However, before explaining Cypress JavaScript in detail, we’ll see why we use JavaScript for Cypress test case automation. Why Cypress With JavaScript? There are several reasons why Cypress and JavaScript work well together: Cypress Is Written in JavaScript Cypress is written in JavaScript, so developers who are already familiar with JavaScript will find it easier to work with Cypress. Cypress Has Extensive Support for JS Frameworks Cypress has good support for popular JavaScript frameworks like React, Vue, and Angular. This means that developers building applications using these frameworks can easily write tests for their applications using Cypress. Cypress Has a Rich API for Interacting With Web Applications Cypress provides a rich API for interacting with web applications, which includes methods for interacting with the DOM, making HTTP requests, and handling events. This API is designed to be easy to use and well-documented, making it easy to write tests using JavaScript. Debugging Support Cypress provides a comprehensive debugging experience, including built-in debugging tools and the ability to step through tests. Community Support There is a vast community of JavaScript developers, which means that plenty of resources are available online to help developers with any issues they encounter while using Cypress. Cypress uses a unique architecture that enables it to run tests in the same context as the application being tested. This means that Cypress has access to the application's DOM, network traffic, and even its backend server. This architecture allows for faster and more reliable tests and a more intuitive and powerful testing experience. Trends of Cypress on GitHub The data below is gathered from the official site of Cypress GitHub repository: Stars: 43.1k Forks: 2.8k Used By: 769k Releases: 302 Contributors: 435 Benefits of Using Cypress JavaScript for Automation Cypress JavaScript provides several features that make it easy to write and run tests, debug issues, and ensure the quality of web applications. We'll explore some of the key features of the Cypress automation tool in detail. Easy Setup Cypress JavaScript is very easy to set up and use. It can be easily installed with npm and requires minimal setup. Cypress also comes with a user-friendly graphical interface, which makes it easy for developers to navigate and use. Comprehensive APIs Cypress JavaScript provides a rich set of APIs for interacting with the DOM, making HTTP requests, and more. This makes it easy to write tests that simulate user interactions with your web application. Real-Time Reloads Cypress JavaScript provides real-time reloads, which means that any changes made to the code or tests are instantly reflected in the browser. This saves developers time and makes it easy to see the impact of changes in real-time. Automatic Waiting Cypress JavaScript automatically waits for assertions to pass and for elements to appear before executing the next command. This makes tests more stable and reduces the likelihood of false negatives. Debugging Cypress JavaScript comes with built-in debugging tools, making it easy to troubleshoot and fix failing tests. Developers can use these tools to inspect the DOM, debug JavaScript code, and more. Time Travel Cypress JavaScript provides a unique feature called "time travel" that allows you to go back and forth in time, inspecting and debugging your application at any point in the test. This feature can save developers a lot of time and effort when trying to debug complex test scenarios. Cross-Browser Testing Cypress JavaScript supports cross-browser testing and can run tests on different browsers. This makes it easy to ensure that your application works as expected across different browsers. Automatic Screenshots and Video Cypress JavaScript can automatically take screenshots and record videos of your tests, making it easy to see what happened during a test run. This can be helpful when trying to identify issues or bugs in your application. Custom Commands Cypress JavaScript allows developers to create custom commands that can be reused across different tests. This makes it easy to create a library of common test commands and reduces the amount of code duplication. Continuous Integration Cypress JavaScript can be easily integrated with popular continuous integration (CI) tools like Jenkins, CircleCI, and Travis CI. This makes it easy to run tests automatically as part of your CI/CD pipeline. How To Install and Set up Cypress Cypress end-to-end testing framework is easy to set up and use. Here are the steps to install and set up Cypress. 1. Install Node.js Cypress requires Node.js to be installed on your system. You can download and install the latest version of Node.js from the official website https://nodejs.org. 2. Create a New Project Create a new directory for your Cypress project and navigate into it using the command line. Run npm init to create a package.json file. 3. Install Cypress Install Cypress by running the following command in the command line. npm install cypress --save-devHere --save-dev is a command-line option used with Node Package Manager (npm) to install and save development dependencies for a project.ORyarn add cypress --devHere, we can see version 12.6.0 installed. Save it as a development dependency in your package.json file. Open Cypress Once installed, you can open Cypress by running the following command in the command line. yarn run cypress open The default folder structure of Cypress is shown below. You can create test cases under the folder “e2e”. Project Structure of Cypress From the screenshots, you can see that Cypress has created a default folder hierarchy when it opens for the first time. Below are the details for each of these folders/files created by Cypress. Cypress: This is the main folder for your Cypress project. It contains all the subfolders and files related to your tests. e2e: This is the main folder to store all your tests. We can add the Basic, End to End Test, API, Visual or Cucumber test here. All your spec files will be here. fixtures: The fixtures/folder is where you can store static data files, such as JSON or CSV files, which your tests can use. For example, you may have a fixture file containing a list of test data that multiple test cases can use. support: This folder contains reusable test code that can be shared across multiple test specs. This can include custom Cypress commands, page objects, or utility functions. There are two files inside the support folder: commands.js and index.js. command.js: This is the file where you add your commonly used functions and custom commands. It includes the common functions that you may call to use in different tests. Cypress created some functions for you, and you can override them here if you want. e2e.js: This file runs before every single spec file. In this file, we keep all global configurations and can modify them as per the requirement. By default, it imports only commands.js, but you can import or require other files to keep things organized. node_modules: All the node packages will be installed in the node_modules directory and available in all the test files. So, in a nutshell, this is the folder where NPM installs all the project dependencies. cypress.config.js: This is a configuration file used by the Cyprestesting framework to customize the behavior of the framework and tests. This file can be used to configure various settings for your tests, such as the base URL for your application, viewport size, test timeout values, and other options. The cypress.config.js file is located in the root of the Cypress project, alongside the cypress folder. When Cypress runs, it will automatically load this file and use the configuration values specified in it. Apart from the above folders, we have some more folders like Screenshots, Downloads, and Videos to store different related files. Basic Constructs of Cypress Cypress uses the same syntax as Mocha for writing test cases. The following are some of the key constructs frequently used in Cypress test development. describe(): This method is used to group related test cases. It takes two arguments: a string that describes the group of test cases and a callback function that contains the individual test cases. it(): This method is used to define a test case. It takes two arguments: a string that describes the test case and a callback function that contains the actual test code before(): This method is used to run a setup function before any test case in a particularly described block. It can be used to set up the test environment, initialize variables, and perform other setup tasks. after(): This method is used to run a cleanup function after all the test cases in a particularly described block have finished running. It can be used to clean up the test environment, close open connections, and perform other cleanup tasks. beforeEach():This method is used to run a setup function before each test case in a particularly described block. It can be used to reset the state of the test environment and perform other setup tasks. afterEach(): This method is used to run a cleanup function after each test case in a particularly described block has finished running. It can be used to reset the state of the test environment and perform other cleanup tasks. .skip(): When dealing with a large codebase and wanting to concentrate on specific tests or subsets of tests, the .skip() function provides a handy means to temporarily prevent certain tests from being executed. Types of Testing Performed Using Cypress JavaScript Cypress is a JavaScript-based testing framework that provides a comprehensive set of features for testing different aspects of web applications, including UI, API, mobile responsiveness, and accessibility. Here's how Cypress JavaScript can be used for each of these types of testing: User Interface (UI) Testing Cypress allows easy and comprehensive UI testing. Cypress provides APIs that allow developers and testers to interact with the web application's UI elements and simulate user interactions while performing Cypress UI automation. The Cypress APIs are capable of validating the application's functionality by allowing users to click on buttons, enter data into forms, and navigate between different pages. It also provides an automatic waiting mechanism that waits for the UI to finish rendering, ensuring that the tests are accurate, reliable, and less flaky. Cypress JavaScript offers significant benefits in dealing with flakiness due to its ability to automatically retry failed tests and provide reloading in real-time. Application Programming Interface (API) Testing Cypress can also be used to test the web application's APIs. Testers can use Cypress to send HTTP requests and validate the responses. It provides a built-in command called cy.request() that enables testers to send HTTP requests and make assertions based on the received responses while performing Cypress API testing. Additionally, Cypress JavaScript supports response interception, mocking, and stubbing, allowing testers to simulate different server responses. Accessibility Testing Cypress can be used to test the accessibility of the web application. Cypress JavaScript provides an integrated accessibility testing library called cypress-axe, which uses the Axe engine to detect accessibility issues and generate accessibility violation reports. With Cypress accessibility testing, it's possible to test the web application's accessibility features and ensure that it's usable by everyone, regardless of any disabilities. Component Testing Cypress is also suitable for component testing, which is useful when working with a modular application that utilizes several components. Cypress JavaScript offers an isolated test environment, making it easy to test individual components and ensure that they work as expected. This testing approach helps to identify and resolve issues early in the development process, reducing the chances of the web application crashing. Mobile Responsiveness Testing Cypress can be used for web application responsive testing across different viewports. Cypress provides a viewport resizing feature, allowing testers to simulate mobile devices' different screen sizes, making it easy to test the web application's responsiveness. Additionally, Cypress JavaScript can integrate with third-party mobile testing platforms like LambdaTest, which offers access to a vast range of mobile devices and operating systems. UI Testing With Cypress JavaScript Cypress provides easy access to the application's DOM, making it easy to manipulate and assert the state of individual elements on the page. It also supports plugins, which can be used to extend its functionality and integrate with other tools and services. These are the reasons Cypress JavaScript is preferred for UI testing. UI Test Case: Example 1 Let’s create a new folder under the e2e folder named “LambdaTest” to perform Cypress UI testing. Create the first spec with the name LoginWithValid_Invalid_Data.cy.js under the folder LambdaTest. In the below test case, we are covering login into LambdaTest with Valid/Invalid data. For demo purposes, we are using the LambdaTest eCommerce Playground site. Use Case Login into the application and update the newsletter subscription JavaScript describe('Login and Subscribe', () => { it('Logs in and subscribes to newsletter', () => { // Visit the site cy.visit('https://ecommerce-playground.lambdatest.io/index.php?route=account/login') // Click the login button cy.get('[value="Login"]').click() // Enter valid email and password cy.get('#input-email').type('lambdatestnew@yopmail.com') cy.get('#input-password').type('Lambda123') // Click the login button cy.get('[value="Login"]').click() // Verify successful login cy.url().should('include', 'index.php?route=account/account') cy.contains('My Account').should('be.visible') // Subscribe to newsletter cy.contains('Newsletter').click() cy.get('#input-newsletter-yes').click({force:true}) cy.get('[value="Continue"]').click() // Wait for subscription success message cy.get('.alert-success').should('contain', 'Success: Your newsletter subscription has been successfully updated!') }) }) Output Below is the output of the above-executed test case. UI Test Case: Example 2 Create the second spec with the name SerachWithValid_InvalidData.cy.js under the folder LambdaTest. In the below test case, we are covering the search with valid and invalid data and verifying the search result. JavaScript describe('Search with Valid/ Invalid data' , () => { beforeEach(() => { cy.visit('https://ecommerce-playground.lambdatest.io/index.php?route=account/login') }) it('Searches for the text "Apple" and displays results', () => { // Enter search data and submit form cy.get('[name="search"]').eq(0).type('Apple') cy.get('.type-text').click() // Verify search results cy.url().should('include', 'search=Apple') cy.contains('Search - Apple').should('be.visible') cy.get('.product-layout').should('have.length.gt', 0) }) it('Displays message with no search results for invalid search term', () => { // Enter search term that returns no results and submit form cy.get('[name="search"]').eq(0).type('abc') cy.get('.type-text').click() // Verify message for no search results cy.contains('There is no product that matches the search criteria.').should('be.visible') }) }) Output Below is the output of the above-executed test case. API Testing with Cypress JavaScript Developers and testers using Cypress can create tests that send HTTP requests to their application's API and validate the corresponding responses. We can use Cypress API to write automated tests that simulate user interactions with the application's API endpoints. This type of testing is especially useful for testing API responses, validating data input and output, and verifying the application’s behavior. It also provides several built-in assertions that can be used to verify the response status code, headers, and body. Here are some key features of using Cypress JavaScript for API testing: HTTP Requests Cypress provides simple and intuitive APIs for making HTTP requests, allowing developers to test different API endpoints and parameters easily. It supports all common HTTP methods, such as GET, POST, PUT, DELETE, etc. Mocking and Stubbing Cypress lets you mock and stub API responses, which is useful when testing API endpoints that rely on third-party services or data sources that may not be available during development/testing. Request and Response Objects Cypress provides request and response objects that allow developers to inspect and manipulate the data being sent and received by APIs. This can help test complex APIs with nested data structures. Authentication and Authorization Cypress supports testing APIs that require authentication or authorization. Testers can use the built-in cy.request method to send authentication tokens or cookies, or they can use plugins to integrate with external services such as OAuth providers. Built-In Support for GraphQL Cypress provides built-in support for testing GraphQL APIs, including a GraphQL-request method that simplifies making GraphQL queries. API Automation Examples For API testing demo purposes, we are taking the example of the site. In the below API testing example, we are covering CRUD operation using the REST API. API Test Case: Example 1 GET Request The GET method is used to retrieve specific data and pass parameters on reading code from the API response. In the example below, we are getting specific user data using the GET method and userId. JavaScript it('GET API Automation Using GoRest API', () => { cy.request({ method: 'GET', url: 'https://gorest.co.in/public/v1'+`/users/${userId}`, headers: { Authorization: 'Bearer 84bb7773414ee1a1247f6830428a2ab051d763d7cb24c97583f10ca96a59ddde', }, }).then((response) => { expect(response.status).to.equal(200); expect(response.body.data.name).to.equal('John Doe'); expect(response.body.data.gender).to.equal('male'); }); }); Output Below is an example of a POST request along with a bearer token for authorization. In POST requests, we retrieve userId for further use by passing data in the body. POST Request JavaScript describe('API Automation Using GoRest API', () => { let randomNumber =Math.floor(Math.random() * 1000); let userId; it('POST API Automation Using GoRest API', () => { const user = { name: 'John Doe', email: "johndoe123"+randomNumber+"@example.com", gender: 'male', status: 'active', }; cy.request({ method: 'POST', url: 'https://gorest.co.in/public/v1/users', headers: { Authorization: 'Bearer 84bb7773414ee1a1247f6830428a2ab051d763d7cb24c97583f10ca96a59ddde', }, body: user, }).then((response) => { userId=response.body.data.id expect(response.status).to.equal(201); expect(response.body.data.name).to.equal(user.name); expect(response.body.data.email).to.equal(user.email); }); }) }); Output PUT Request Below is an example of a PUT request and a bearer token for authorization. In PUT requests, we update the existing data by passing the userId for which we want to update the data. JavaScript it('PUT API Automation Using GoRest API', () => { const user = { name: 'Time Cook', email: "TimCook123"+randomNumber+"@example.com", gender: 'male', status: 'active', }; cy.request({ method: 'PUT', url: 'https://gorest.co.in/public/v1'+`/users/${userId}`, headers: { Authorization: 'Bearer 84bb7773414ee1a1247f6830428a2ab051d763d7cb24c97583f10ca96a59ddde', }, body: user, }).then((response) => { expect(response.status).to.equal(200); expect(response.body.data.name).to.equal(user.name); expect(response.body.data.email).to.equal(user.email); }); }); Output DELETE Request The created record can be deleted using the delete method. We can pass the userId and an authorization token in the header. JavaScript it('DELETE API Automation Using GoRest API', () => { cy.request({ method: 'DELETE', url: 'https://gorest.co.in/public/v1'+`/users/${userId}`, headers: { Authorization: 'Bearer 84bb7773414ee1a1247f6830428a2ab051d763d7cb24c97583f10ca96a59ddde', }, }).then((response) => { expect(response.status).to.equal(204); }); }); Output API Automation Using ‘Cypress-Plugin-Api’ Plugin The 'cypress-plugin-api' is a useful plugin for Cypress that provides a simplified and streamlined API for making HTTP requests and performing API testing in your Cypress tests. There are several benefits of using this plugin, some of which are listed below. cy.api() command informs about the API call, such as URL, headers, response, and more to the UI frame, and this info can be viewed in time-travel snapshots. Simple table for viewing cookies. JSON data object and array folding. Color coding of methods in UI view and in the timeline. Let’s set up cypress-plugin-api. Steps 1 Install the plugin 'cypress-plugin-api' using the below commands. npm i cypress-plugin-api Or yarn add cypress-plugin-api Steps 2 Import the plugin under the path cypress/support/e2e.js file. import 'cypress-plugin-api' or require('cypress-plugin-api') package.json looks like below: JavaScript { "name": "cypress_javascript", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "Kailash Pathak", "license": "ISC", "devDependencies": { "cypress": "^12.6.0" }, "dependencies": { "cypress-plugin-api": "^2.10.3" } } Add the below commands under the Cypress/e2e.js file are below: import './commands' import 'cypress-plugin-api' Let's take some examples to automate using the plugin cypress-plugin-api. Create ApiTestCaseUsingPlugin.cy.js with Methods (GET, POST, PUT, DELETE) Below is an example of Methods (GET, POST, PUT, DELETE) with their output of the execution of Test Cases. The data for Body, Response, Headers, and Cookies may be seen in the Cypress App UI in the screenshot below. The cy.api() works similarly to the cy.request() command. The benefit of using cy.api() is it provides the response, headers, and cookies are all visible, and also prints details about the call in your Cypress runner, as shown below. GET Request JavaScript it('GET API Automation Using GoRest API', () => { cy.api({ method: 'GET', url: 'https://gorest.co.in/public/v1'+`/users/${userId}`, headers: { Authorization: 'Bearer 84bb7773414ee1a1247f6830428a2ab051d763d7cb24c97583f10ca96a59ddde', }, }).then((response) => { expect(response.status).to.equal(200); expect(response.body.data.name).to.equal('John Doe'); expect(response.body.data.gender).to.equal('male'); }); }); Output GET Request POST Request JavaScript it('POST API Automation Using GoRest API', () => { const user = { name: 'John Doe', email: "johndoe123"+randomNumber+"@example.com", gender: 'male', status: 'active', }; cy.request({ method: 'POST', url: 'https://gorest.co.in/public/v1/users', headers: { Authorization: 'Bearer 84bb7773414ee1a1247f6830428a2ab051d763d7cb24c97583f10ca96a59ddde', }, body: user, }).then((response) => { userId=response.body.data.id expect(response.status).to.equal(201); expect(response.body.data.name).to.equal(user.name); expect(response.body.data.email).to.equal(user.email); }); }); Output POST Request PUT Request JavaScript it('PUT API Automation Using GoRest API', () => { const user = { name: 'Time Cook', email: "TimCook123"+randomNumber+"@example.com", gender: 'male', status: 'active', }; cy.request({ method: 'PUT', url: 'https://gorest.co.in/public/v1'+`/users/${userId}`, headers: { Authorization: 'Bearer 84bb7773414ee1a1247f6830428a2ab051d763d7cb24c97583f10ca96a59ddde', }, body: user, }).then((response) => { expect(response.status).to.equal(200); expect(response.body.data.name).to.equal(user.name); expect(response.body.data.email).to.equal(user.email); }); }); Output PUT Request DELETE Request JavaScript it('DELETE API Automation Using GoRest API', () => { cy.request({ method: 'DELETE', url: 'https://gorest.co.in/public/v1'+`/users/${userId}`, headers: { Authorization: 'Bearer 84bb7773414ee1a1247f6830428a2ab051d763d7cb24c97583f10ca96a59ddde', }, }).then((response) => { expect(response.status).to.equal(204); }); }); Output DELETE Request Accessibility Testing With Cypress JavaScript Accessibility testing is the process of evaluating a website or application to ensure that it can be used by people with disabilities. Cypress JavaScript provides an accessibility plugin called cypress-axe that can be used to check for accessibility issues on a website. The plugin employs the axe-core open-source library to conduct accessibility assessments, which detect accessibility infractions under the Web Content Accessibility Guidelines (WCAG). To use the cypress-axe plugin, you first need to install it using npm. Once installed, you can import the plugin into your Cypress test file and use the cy.checkA11y() method to check for accessibility violations. Executing this command triggers the axe-core library to scan the present page and disclose any accessibility problems it detects. Install the Cypress accessibility plugin by running the following command in your project directory: npm install --save-dev cypress-axe Once the installation is complete, you can import the plugin in your Cypress test file by adding the following line at the top: import 'cypress-axe'; Now, you can write your accessibility tests using the cy.injectAxe() and cy.checkA11y() commands provided by the plugin. JavaScript describe('Accessibility testing with cypress-axe', () => { it('should not have any accessibility violations', () => { cy.visit('https://ecommerce-playground.lambdatest.io/'); cy.injectAxe(); cy.checkA11y(); }); }); This test uses the cy.visit() command to open the website, and the cy.injectAxe() command to inject the accessibility plugin. The cy.checkA11y() command then checks the page for accessibility violations. Output In the screenshot, you can see nine accessibility violations. Component Testing With Cypress JavaScript Component testing is a type of software testing in which individual components of a software system are tested in isolation from the rest of the system. What Is Component Testing? The purpose of component testing is to validate the functionality and behavior of individual software modules, such as functions, classes, or methods, to ensure that they work as expected and meet the requirements specified in the design. Component testing primarily aims to uncover flaws or glitches in the software modules before their integration into the broader system, thereby minimizing the likelihood of downstream breakdowns and simplifying the process of isolating and remedying issues. By testing each component in isolation, developers can gain confidence that each module is working correctly, which can improve overall system quality and reduce the time and cost of testing and debugging. Cypress for Component Testing With Cypress, you can use its built-in testing API to create tests that target specific components of your application. Cypress JavaScript provides a <mount> command that can be used to mount a React component within a Cypress test runner, making it possible to test the component's behavior and interactions with other components. Advantages of Component Testing With Cypress JavaScript Early Detection of Defects Component testing helps detect defects or issues in the software code early in the development process. By identifying and fixing issues early, it becomes easier and cheaper to fix them than when they are detected later in the software development life cycle. Improved Reusability Component testing promotes code reusability since individual components can be reused in other applications. Faster Development Component testing helps to speed up the development process. By catching defects early, developers can fix them quickly, which leads to faster development cycles and quicker delivery of software. Better Maintainability Component testing helps to improve the maintainability of the software code. By ensuring that each component of the software is functioning correctly, it becomes easier to maintain the software in the long run. Isolation Since each component is tested in isolation, defects can be easily isolated and identified, making it easier to fix them. Example of Component Testing Before taking the example, let's do some prerequisite steps for creating component testing. Prerequisite Node is installed. VS Code is installed. Testing React components with Cypress involves setting up a testing environment and writing test cases to ensure that the components behave as expected. Here are the steps to get started: Let’s set up a React component. Step 1: Run the below command in the terminal. npx create-react-app my-new-sample-app Step 2: In the root directory and run the below command to launch React app and install Cypress (if not installed previously). cd my-new-sample-app npm start npm install cypress -D Step 3: Run the command to open Cypress runner. npx cypress open or yarn cypress open Step 4: From the below screenshot select ‘Component Testing”. Step 5: After selecting ‘Component Testing’ from the above, the below-attached screen gets opened. Select the option ‘Create React App’ from the above screen. Step 6: Click on Next step from the above screen and wait for all dependencies to install. Step 7: Click on Continue button from the above screen. Step 8: Click on Continue button from the above screen. Select the browser, e.g., Chrome, and click ‘Start Component Testing in Chrome.’ Step 9: Select ‘Create new spec’ and enter the path of the new spec and click ‘Create spec.’ Step 10: Click on Okay, run the spec button. Below is the folder structure after installing the React app. In this structure, the src folder contains all the source code for the project, while the components subfolder is where the component files are stored, and it is from these files that the test files in the components folder at the top level of the project will import the components to be tested. Each component has its folder, which includes the component file itself (component1.js, component2.js, etc.) and its corresponding test file, e.g. (component 1.test.js, component 2.test.js, etc.). Let’s create a component test case. Create a ‘counter component’ inside the src folder and give its name lambadaTest.jsx. JavaScript import { useState } from 'react' export default function Counter({ initial = 0 }) { const [count, setCount] = useState(initial) return ( <div style={{ padding: 30 }> <button style={{ color: "black", backgroundColor: "green", margin: 10 } aria-label="decrement" onClick={() => setCount(count - 1)}> - </button> <span data-cy="counter">{count}</span> <button style={{ color: "black", backgroundColor: "green", margin: 10 } aria-label="increment" onClick={() => setCount(count + 1)}> + </button> </div> ) } Let's run the created component test cases by running the below command. npx cypress open --component In the below screenshot, we can see that component tests are executed. Let’s cover some more scenarios using this Counter React component. Test Scenario Click on (+) to increment the count. Click on (-) to decrement the count. JavaScript import Counter from '../../src/lambadaTest' describe("<Counter>", () => { const counterSelector = '[data-cy="counter"]'; const incrementSelector = "[aria-label=increment]"; const decrementSelector = "[aria-label=decrement]"; it("Do two time increment then one time decrement the count ", () => { cy.mount(<Counter />); //Do two time Increment the Count cy.get(incrementSelector).click(); cy.get(incrementSelector).click(); // Put Assert cy.get(counterSelector).should("contain.text", 2); //Do the decrement now cy.get(decrementSelector).click(); // Put Assert cy.get(counterSelector).should("have.text", "1"); // Put Assert color cy.get(decrementSelector) .should("have.css", "color") .and("eq", "rgb(0, 0, 0)"); // Assert background color cy.get(decrementSelector) .should("have.css", "background-color") .and("eq", "rgb(0, 128, 0)"); }); it("Do two time decrement then one time increment the count ", () => { cy.mount(<Counter />); //Two time decrement the count cy.get(decrementSelector).click(); cy.get(decrementSelector).click(); // Assert cy.get(counterSelector).should("have.text", "-2"); //Then increment the count cy.get(incrementSelector).click(); cy.get(counterSelector).should("have.text", "-1"); // Put Assert color cy.get(decrementSelector) .should("have.css", "color") .and("eq", "rgb(0, 0, 0)"); // Put Assert background color cy.get(decrementSelector) .should("have.css", "background-color") .and("eq", "rgb(0, 128, 0)"); }); }); Output Scenario 1 In the log, first, we increment the count two times and then decrement the count once. Scenario 2 In the log, first, we decrement the count two times and then increment the count once. Mobile Responsiveness Testing With Cypress JavaScript Cypress mobile responsiveness testing is a type of testing that focuses on evaluating the responsiveness and compatibility of a website. It involves using Cypress, an end-to-end testing framework, to automate the process of simulating mobile device interactions and testing the behavior of a website or application across different screen sizes and resolutions. In Cypress JavaScript, the cy.viewport() method is used to set the dimensions of the browser's viewport. Here are some of the different methods that can be used with cy.viewport() to modify the viewport. cy.viewport() with fixed dimensions As mentioned earlier, the cy.viewport() method takes two arguments, width and height, to set the viewport dimensions. For example: cy.viewport(1280, 720) // set viewport to 1280 x 720 pixels cy.viewport() with predefined device Cypress provides several predefined device sizes that can be used with cy.viewport() to simulate different devices. For example: cy.viewport('iphone-6') // set viewport to iPhone 6 size (375 x 667 pixels) cy.viewport('ipad-mini') // set viewport to iPad Mini size (768 x 1024 pixels) cy.viewport() with custom device sizes Developers can also define custom device sizes using the cy.viewport() method. For example: cy.viewport(550, 750) // set viewport to custom size (550 x 750 pixels) cy.viewport() with landscape orientation To simulate landscape orientation, a third argument can be added to cy.viewport(). For example: cy.viewport(1024, 768, 'landscape') // set viewport to 1024 x 768 pixels in landscape orientation cy.viewport() with dynamic sizing Cypress also provides the ability to dynamically set the viewport size based on the screen size of the device running the test. For example: cy.viewport('macbook-15') // set viewport to MacBook Pro 15-inch size on a desktop computer Mobile Responsiveness Test Case Example In the below example, we are using the LambdaTest eCommerce Playground for mobile responsiveness testing. Mobile Responsiveness: Example 1 In this example, we verify that the email and password fields are visible and the label of the email and password fields. JavaScript describe('Mobile Responsiveness Testing', () => { it('Loads the login page in iphone-6', () => { cy.viewport('iphone-6') // set viewport to iPhone 6 size cy.visit('https://ecommerce-playground.lambdatest.io/index.php?route=account/login') cy.get('#input-email').should('be.visible') // check that email input is still visible cy.get('#input-password').should('be.visible') // check that password input is still visible cy.get('input[type="submit"]').should('be.visible') // check that login button is still visible cy.get('label[for="input-email"]').should('have.text', 'E-Mail Address') // check that label for email input is visible and has correct text cy.get('label[for="input-password"]').should('have.text', 'Password') // check that label for password input is visible and has correct text }) }) Code Walkthrough This code uses the viewport command to set the size of the browser window to different mobile device sizes. Then it uses the Cypress get command to check that certain elements on the page are visible and have the correct text. You can check additional tests to check other aspects of the page's responsiveness, such as how images or text scale at different screen sizes. Presented here is an instance of mobile responsiveness testing carried out on an iPhone 6. Similarly, responsiveness testing can be performed on other viewports by modifying the device name in the cy.viewport() method. Output Below is the output of the above-executed test case. Mobile Responsiveness: Example 2 Below is an example for searching the text in the LambdaTest eCommerce Playground for mobile responsiveness testing. Code Walkthrough This code uses the viewport (iphone-x) in beforeEach() hook. In the test case, we are searching the text ‘Apple’ and verify the text after searching. beforeEach() is a hook that runs a block of code before each test case within a test suite. JavaScript describe.only('Mobile Responsiveness Test for search the text in E-commerce Playground Site', () => { beforeEach(() => { cy.viewport('iphone-x') cy.visit('https://ecommerce-playground.lambdatest.io/') }) it('Searches for the text "Apple" and displays results', () => { // Enter search data and submit form cy.get('[name="search"]').eq(1).click({force: true}).type('Apple') cy.get('.type-icon').click() // Verify search results cy.url().should('include', 'search=Apple') cy.contains('Search - Apple').should('be.visible') cy.get('.product-layout').should('have.length.gt', 0) }) }) Output Below is the output of the above-executed test case. Running Cypress Test Cases Locally You can run the test case from the command line or use Cypress runner. Let’s run test cases using Cypress runner. To open the Cypress test runner, run the following command. yarn run cypress open The above command will open the Cypress test runner with the existing test cases. You can select the browser on which you want to run the test case. Let’s execute three test cases in the local environment. LoginAndSubscribeForNewsletter.cy.js, MobileResponsivenessTesting.cy.js, and SearchWithValid_InvalidData.cy.js File Name: LoginAndSubscribeForNewsletter.cy.js JavaScript describe('Login and Subscribe', () => { it('Logs in and subscribes to newsletter', () => { // Visit the site cy.visit('https://ecommerce-playground.lambdatest.io/index.php?route=account/login') // Click the login button cy.get('[value="Login"]').click() // Enter valid email and password cy.get('#input-email').type('lambdatestnew@yopmail.com') cy.get('#input-password').type('Lambda123') // Click the login button cy.get('[value="Login"]').click() // Verify successful login cy.url().should('include', 'index.php?route=account/account') cy.contains('My Account').should('be.visible') // Subscribe to newsletter cy.contains('Newsletter').click() cy.get('#input-newsletter-yes').click({force:true}) cy.get('[value="Continue"]').click() // Wait for subscription success message cy.get('.alert-success').should('contain', 'Success: Your newsletter subscription has been successfully updated!') }) }) File Name: MobileResponsivenessTesting.cy.js describe('Mobile Responsiveness Testing', () => { it('Loads the login page in iphone-6', () => { cy.viewport('iphone-6') // set viewport to iPhone 6 size cy.visit('https://ecommerce-playground.lambdatest.io/index.php?route=account/login') cy.get('#input-email').should('be.visible') // check that email input is still visible cy.get('#input-password').should('be.visible') // check that password input is still visible cy.get('input[type="submit"]').should('be.visible') // check that login button is still visible cy.get('label[for="input-email"]').should('have.text', 'E-Mail Address') // check that label for email input is visible and has correct text cy.get('label[for="input-password"]').should('have.text', 'Password') // check that label for password input is visible and has correct text }) File Name: SearchWithValid_InvalidData.cy.js describe('Login Test', () => { beforeEach(() => { cy.visit('https://ecommerce-playground.lambdatest.io/index.php?route=account/login') }) it('Searches for the text "Apple" and verify result', () => { // Enter search data and submit form cy.get('[name="search"]').eq(0).type('Apple') cy.get('.type-text').click() // Verify search results cy.url().should('include', 'search=Apple') cy.contains('Search - Apple').should('be.visible') cy.get('.product-layout').should('have.length.gt', 0) }) it('Displays message with no search results for invalid search term', () => { // Enter search term that returns no results and submit form cy.get('[name="search"]').eq(0).type('abc') cy.get('.type-text').click() // Verify message for no search results cy.contains('There is no product that matches the search criteria.').should('be.visible') }) }) Let's execute the above test cases. When we run the command yarn run cypress open, the below screen is opened. Test Execution LoginAndSubscribeForNewsletter.cy.js MobileResponsivenessTesting.cy.js SearchWithValid_InvalidData.cy.js Wrapping Up In conclusion, Cypress test automation using JavaScript can significantly improve the speed and efficiency of software testing. As JavaScript supports many testing frameworks, developers can leverage it. Also, JavaScript is very easy to learn. Developers can learn JavaScript very fast and start development.
Much has been written about the impact of AI tooling on software development, or indeed on any creative endeavor. Some of those blogs may already be written by AI, who knows? If the benefits for mundane coding tasks today are any foretaste of what lies ahead, I dare not contemplate what the next year will bring, let alone the next decade. I’m not overly worried. The price of job security was always continuous upgrading of your skillset - which is why I’m studying for the Oracle Certified Professional Java SE 17 developer exam. The OCP is reassuringly and infuriatingly old-school. It grills you on arrays, shorts, ObjectOutputStream, the flip bit operator ~, and much you’re probably not going to write or encounter. What is the point? I’ll tell you. On the one hand, the programming profession has changed beyond recognition from when I started in 1999 and long before that. I look forward to veteran Jim Highsmith’s upcoming book Wild West to Agile. It’s supposed to be liberally sprinkled with personal anecdotes from the era of punch cards and overnight compiles. The teasers remind me of the classic Four Yorkshiremen sketch by Monty Python, boasting how tough they had it. “We lived eighteen to a room! – Luxury! We lived in a septic tank.” On the other hand, much less has changed at the level of methods and classes or loops and logic, despite the mushrooming complexity and range of APIs and tooling. Real language innovations are rare and the challenges for learners remain the same. Autocomplete doesn’t make understanding a tail recursive function any easier, but before Stack Overflow, it made sense to memorize such common patterns, because it was too much bother to look them up. It still makes sense. Professor Dijkstra Would Not Approve of Copilot You can safely assume that a famous Turing award winner like Edsger Dijkstra (1930-2002), would have been horrified by GitHub Copilot. He preferred doing his mathematical proofs on a blackboard and believed that software engineering had little to do with academic computer science and that the latter was not even about computers. After all, we don’t call a surgeon’s work "knife-science." Studying for the OCP means honing your Spartan mindset. Taking it unprepared, even as an experienced developer, is a waste of money. You will fail. Because it’s not a test of your design intuitions or clean coding hygiene. It calls on your knowledge of arcane details and APIs, but even more on your short-term memory skills to grasp some quite insane code. Boy, have these skills gone rusty! I wrote earlier that IntelliJ has made us all a bit stupid and it’s true. I’m still making plenty of mistakes. Factual knowledge gaps don’t trip me up anymore, but the time constraint does. The two-minute average you can spend on each question is tight. Yes, there are short questions requiring a single answer, but they don’t offset the ones with convoluted sample code, where you rack your brains for five minutes over the effect of changing one statement and fail to spot the missing semicolon after a switch expression, so the whole mess would never compile. Three Reasons Not To Bother There are reasons not to bother with this self-torture, but there’s a flavor of cognitive dissonance to them. They’re attractive to let yourself off the hook. First, what’s the point of playing human compiler and virtual machine over code samples that solve no real-world task and are only designed to confuse? The point is to train your mind muscles, to sharpen the saw. Nobody disputes that IDEs and their real-time compilation warnings are a great productivity boost. Nobody edits a big Java project in a plaintext editor. That would be inefficient and error-prone. But you want to know what you’re doing and at least understand the warnings. I don’t do dumbbell exercises for the sake of dumbbell exercises. I do them so I can still lift my own groceries when I retire. Secondly, neither this OCP nor any of its predecessors teach you how to write clean code, much less design a complex product. It has nothing to say about testing. It’s a thorough foundation of the language toolkit, but no more. Calling the exam inadequate for that reason is a strawman argument. You pooh-pooh it for not teaching you something it never claimed to teach you in the first place. If you take an advanced French grammar course, it won’t teach you how to write a novel either. A third bone of contention is the OCP’s focus on little-used and legacy features. Who uses serializable object streams when the whole world has been using JSON for years? Well, there’s an awful lot of twenty-year-old, pre-version 5 legacy around, and you shouldn’t be taken aback by it. Also, in the makers’ defense: deprecated features or ways of working do eventually make their way to the exit. The SCJP 6 I took in 2010 had some tough questions on low-level thread handling, all of which are now abstracted behind the concurrency API. We can expect arrays to go the same way, but no time soon. To Be Continued I have much more to say on each of the topics I raised, so I want to make this a series of blogs. I want to explore and explain my personal motivations throughout the process and hope to share useful advice on how to make the journey a success. The aim is not cramming to get a certificate and promptly forget what you learned. You’re not in high school. The aim is to respect the importance of certain mental skills we shouldn’t allow to get rusty. This will make you a better and happier software developer. I have the following topics in mind for the next months. Motivation and the "okay point." Do you enjoy learning for learning’s sake, or is it a means to an end? If so, you will master the bare minimum you need to get the job done and give up once you reach the okay point. This happens to seniors, especially when they are burdened with many non-coding tasks. The only effective way to learn is to make it fun, compelling, and practical. Always learn with the IDE at your side and disable all the clever assistants. I’m compiling a collection of mnemonics and rhymes, which I hope to expand. When it comes to remembering, the sillier and crazier the better – actually, the lewder the better, but I’ll leave that to your own imagination.
Previously we checked on ReentRantLock and its fairness. One of the things we can stumble upon is the creation of a Condition. By using Condition, we can create mechanisms that allow threads to wait for specific conditions to be met before proceeding with their execution. Java public interface Condition { void await() throws InterruptedException; void awaitUninterruptibly(); long awaitNanos(long nanosTimeout) throws InterruptedException; boolean await(long time, TimeUnit unit) throws InterruptedException; boolean awaitUntil(Date deadline) throws InterruptedException; void signal(); void signalAll(); } The closest we came to that so far is the wait Object Monitor method. A Condition is bound to a Lock and a thread cannot interact with a Condition and its methods if it does not have a hold on that Lock. Also, Condition uses the underlying lock mechanisms. For example, signal and signalAll will use the underlying queue of the threads that are maintained by the Lock, and will notify them to wake up. One of the obvious things to implement using Conditions is a BlockingQueue. Worker threads process data and publisher threads dispatch data. Data are published on a queue, worker threads will process data from the queue, and then they should wait if there is no data in the queue. For a worker thread, if the condition is met the flow is the following: Acquire the lock Check the condition Process data Release the lock If the condition is not met, the flow would slightly change to this: Acquire the lock Check the condition Wait until the condition is met Re-acquire the lock Process data Release the lock The publisher thread, whenever it adds a message, should notify the threads waiting on the condition. The workflow would be like this: Acquire the lock Publish data Notify the workers Release the lock Obviously, this functionality already exists through the BlockingQueue interface and the LinkedBlockingDeque and ArrayBlockingQueue implementations. We will proceed with an implementation for the sake of the example. Let’s see the message queue: Java package com.gkatzioura.concurrency.lock.condition; import java.util.LinkedList; import java.util.Queue; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class MessageQueue<T> { private Queue<T> queue = new LinkedList<>(); private Lock lock = new ReentrantLock(); private Condition hasMessages = lock.newCondition(); public void publish(T message) { lock.lock(); try { queue.offer(message); hasMessages.signal(); } finally { lock.unlock(); } } public T receive() throws InterruptedException { lock.lock(); try { while (queue.isEmpty()) { hasMessages.await(); } return queue.poll(); } finally { lock.unlock(); } } } Now let’s put it into action: Java MessageQueue<String> messageQueue = new MessageQueue<>(); @Test void testPublish() throws InterruptedException { Thread publisher = new Thread(() -> { for (int i = 0; i < 10; i++) { String message = "Sending message num: " + i; log.info("Sending [{}]", message); messageQueue.publish(message); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); Thread worker1 = new Thread(() -> { for (int i = 0; i < 5; i++) { try { String message = messageQueue.receive(); log.info("Received: [{}]", message); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); Thread worker2 = new Thread(() -> { for (int i = 0; i < 5; i++) { try { String message = messageQueue.receive(); log.info("Received: [{}]", message); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); publisher.start(); worker1.start(); worker2.start(); publisher.join(); worker1.join(); worker2.join(); } That’s it! Our workers processed the expected messages and waited when the queue was empty.
With the growth of the application modernization demands, monolithic applications were refactored to cloud-native microservices and serverless functions with lighter, faster, and smaller application portfolios for the past years. This was not only about rewriting applications, but the backend data stores were also redesigned in terms of dynamic scalability, high performance, and flexibility for event-driven architecture. For example, traditional data structures in relational databases started to move forward to a new approach that enables to storage and retrieval of key-value and document data structures using NoSQL databases. However, faster modernization presents more challenges for Java developers in terms of steep learning curves about new technologies adoption and retaining current skillsets with experience. For instance, Java developers need to rewrite all existing Java applications to Golang and JavaScript for new serverless functions and learn new APIs or SDKs to process dynamic data records by new modernized serverless applications. This article will take you through a step-by-step tutorial to learn how Quarkus enables Java developers to implement serverless functions on AWS Lambda to process dynamic data on AWS DynamoDB. Quarkus enables developers not only to optimize Java applications for superfast startup time (e.g., milliseconds) and tiny memory footprints (e.g., less than 100 MB) for serverless applications, but developers can also use more than XX AWS extensions to deploy Java applications to AWS Lambda and access AWS DynamoDB directly without steep learning curves. Creating a New Serverless Java Project Using Quarkus We’ll use the Quarkus command to generate a new project with required files such as Maven Wrapper, Dockerfiles, configuration properties, and sample code. Find more information about the benefits of the Quarkus command (CLI) here. Run the following Quarkus command in your working directory. Shell quarkus create piggybank --java=17 You need to use the JDK 17 version since AWS Lambda currently supports JDK 17 as the latest version by default Java runtime (Corretto). Let’s start Quarkus Live Coding, also known as quarkus dev mode, using the following command. Shell cd piggybank && quarkus dev mode Developing Business Logic for Piggybank Now let's add a couple of Quarkus extensions to create a DynamoDB entity and relevant abstract services using the following Quarkus command in the Piggybank directory. Shell quarkus ext add amazon-dynamodb resteasy-reactive-jackson The output should look like this. Java [SUCCESS] ✅ Platform io.quarkus.platform:quarkus-amazon-services-bom has been installed [SUCCESS] ✅ Extension io.quarkiverse.amazonservices:quarkus-amazon-dynamodb has been installed [SUCCESS] ✅ Extension io.quarkus:quarkus-resteasy-reactive-jackson has been installed Creating an Entity Class You will create a new data model (entry.java) file to define Java attributes that map into the fields in DynamoDB. The Java class should look like the following code snippet (you can find the solution in the GitHub repository): Java @RegisterForReflection public class Entry { public Long timestamp; public String accountID; ... public Entry() {} public static Entry from(Map<String, AttributeValue> item) { Entry entry = new Entry(); if (item != null && !item.isEmpty()) { entry.setAccountID(item.get(AbstractService.ENTRY_ACCOUNTID_COL).s()); ... } return entry; } ... } The @RegisterForReflectionannotation instructs Quarkus to keep the class and its members during the native compilation. Find more information here. Creating an Abstract Service Now you will create a new AbstractService.java file to consist of helper methods that prepare DynamoDB to request objects for reading and adding items to the table. The code snippet should look like this (find the solution in the GitHub repository): Java public class AbstractService { public String accountID; ... public static final String ENTRY_ACCOUNTID_COL = "accountID"; ... public String getTableName() { return "finance"; } protected ScanRequest scanRequest() { return ScanRequest.builder().tableName(getTableName()) .attributesToGet(ENTRY_ACCOUNTID_COL, ENTRY_DESCRIPTION_COL, ENTRY_AMOUNT_COL, ENTRY_BALANCE_COL, ENTRY_DATE_COL, ENTRY_TIMESTAMP, ENTRY_CATEGORY).build(); } ... } Adding a Business Layer for REST APIs Create a new EntryService.java file to extend the AbstractService class that will be the business layer of your application. This logic will store and retrieve the entry data from DynamoDB synchronously. The code snippet should look like this (solution in the GitHub repository): Java @ApplicationScoped public class EntryService extends AbstractService { @Inject DynamoDbClient dynamoDB; public List<Entry> findAll() { List<Entry> entries = dynamoDB.scanPaginator(scanRequest()).items().stream() .map(Entry::from) .collect(Collectors.toList()); entries.sort((e1, e2) -> e1.getDate().compareTo(e2.getDate())); BigDecimal balance = new BigDecimal(0); for (Entry entry : entries) { balance = balance.add(entry.getAmount()); entry.setBalance(balance); } return entries; } ... } Creating REST APIs Now you'll create a new EntryResource.java file to implement REST APIs to get and post the entry data from and to DynamoDB. The code snippet should look like the below (solution in the GitHub repository): Java @Path("/entryResource") public class EntryResource { SimpleDateFormat piggyDateFormatter = new SimpleDateFormat("yyyy-MM-dd+HH:mm"); @Inject EntryService eService; @GET @Path("/findAll") public List<Entry> findAll() { return eService.findAll(); } ... } Verify the Business Services Locally First, we need to install a local DynamoDB that the piggy bank services access. There’re a variety of ways to stand up a local DynamoDB such as downloading an executable .jar file, running a container image, and deploying by Apache Maven repository. Today, you will use the Docker compose to install and run DynamoDB locally. Find more information here. Create the following docker-compose.yml file in your local environment. YAML version: '3.8' services: dynamodb-local: command: "-jar DynamoDBLocal.jar -sharedDb -dbPath ./data" image: "amazon/dynamodb-local:latest" container_name: dynamodb-local ports: - "8000:8000" volumes: - "./docker/dynamodb:/home/dynamodblocal/data" working_dir: /home/dynamodblocal Then, run the following command-line command. Shell docker-compose up The output should look like this. Shell [+] Running 2/2 ⠿ Network quarkus-piggybank_default Created 0.0s ⠿ Container dynamodb-local Created 0.1s Attaching to dynamodb-local dynamodb-local | Initializing DynamoDB Local with the following configuration: dynamodb-local | Port: 8000 dynamodb-local | InMemory: false dynamodb-local | DbPath: ./data dynamodb-local | SharedDb: true dynamodb-local | shouldDelayTransientStatuses: false dynamodb-local | CorsParams: null dynamodb-local | Creating an Entry Table Locally Run the following AWS DynamoDB API command to create a new entry table in the running DynamoDB container. Shell aws dynamodb create-table --endpoint-url http://localhost:8000 --table-name finance --attribute-definitions AttributeName=accountID,AttributeType=S AttributeName=timestamp,AttributeType=N --key-schema AttributeName=timestamp,KeyType=HASH AttributeName=accountID,KeyType=RANGE --provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5 --table-class STANDARD Adding DynamoDB Clients Configurations DynamoDB clients are configurable in the application.properties programmatically. You also need to add to the classpath a proper implementation of the sync client. By default, the extension uses the java.net.URLConnection HTTP client. Open the pom.xml file and copy the following dependency right after the quarkus-amazon-dynamodb dependency. XML <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>url-connection-client</artifactId> </dependency> Then, add the following key and value to the application.properties to specify your local DynamoDB's endpoint. Java %dev.quarkus.dynamodb.endpoint-override=http://localhost:8000 Starting Quarkus Live Coding Now you should be ready to verify the Piggybank application using Quarkus Dev mode and local DynamoDB. Run the Quarkus Dev mode using the following Quarkus command. Shell quarkus dev The output should end up this. Shell __ ____ __ _____ ___ __ ____ ______ --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \ --\___\_\____/_/ |_/_/|_/_/|_|\____/___/ [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.quarkus xx.xx.xx.) s2023-04-30 21:14:49,824 INFO [io.quarkus] (Quarkus Main Thread) Installed features: [amazon-dynamodb, cdi, resteasy-reactive, resteasy-reactive-jackson, smallrye-context-propagation, vertx] -- Tests paused Press [r] to resume testing, [o] Toggle test output, [:] for the terminal, [h] for more options> Run the following curl command to insert several expense items into the piggybank account (entry table). Shell curl -X POST http://localhost:8080/entryResource -H 'Content-Type: application/json' -d '{"accountID": "Food", "description": "Shrimp", "amount": "-20", "balance": "0", "date": "2023-02-01"}' curl -X POST http://localhost:8080/entryResource -H 'Content-Type: application/json' -d '{"accountID": "Car", "description": "Flat tires", "amount": "-200", "balance": "0", "date": "2023-03-01"}' curl -X POST http://localhost:8080/entryResource -H 'Content-Type: application/json' -d '{"accountID": "Payslip", "description": "Income", "amount": "2000", "balance": "0", "date": "2023-04-01"}' curl -X POST http://localhost:8080/entryResource -H 'Content-Type: application/json' -d '{"accountID": "Utilities", "description": "Gas", "amount": "-400", "balance": "0", "date": "2023-05-01"}' Verify the stored data using the following command. Shell curl http://localhost:8080/entryResource/findAll The output should look like this. JSON [{"accountID":"Food","description":"Shrimp","amount":"-20","balance":"-30","date":"2023-02-01"},{"accountID":"Drink","description":"Wine","amount":"-10","balance":"-10","date":"2023-01-01"},{"accountID":"Payslip","description":"Income","amount":"2000","balance":"1770","date":"2023-04-01"},{"accountID":"Car","description":"Flat tires","amount":"-200","balance":"-230","date":"2023-03-01"},{"accountID":"Utilities","description":"Gas","amount":"-400","balance":"1370","date":"2023-05-01"}] You can also find a certain expense based on accountID. Run the following curl command again. Shell curl http://localhost:8080/entryResource/find/Drink The output should look like this. JSON {"accountID":"Drink","description":"Wine","amount":"-10","balance":"-10","date":"2023-01-01"} Conclusion You learned how Quarkus enables developers to write serverless functions that connect NoSQL databases to process dynamic data. To stand up local development environments, you quickly ran the local DynamoDB image using the docker-compose command as well. Quarkus also provide various AWS extensions including amazon-dynamodb to access the AWS cloud services directly from your Java applications. Find more information here. In the next article, you’ll learn how to create a serverless database using AWS DynamoDB and build and deploy your local serverless Java functions to AWS Lambda by enabling SnapStart.
Here, in just a few simple steps, we'll develop a mobile multiplayer game using Unity and Oracle's Backend for Parse. A Brief Introduction of Unity and Parse Platform Unity is a cross-platform game engine developed by Unity Technologies, first announced and released in June 2005 at Apple Worldwide Developers Conference as a Mac OS X game engine. The engine has since been gradually extended to support a variety of desktop, mobile, console, and virtual reality platforms. The Parse Platform is a back-end for mobile developers, open-sourced by Facebook/Meta in 2016, that helps to store data in the cloud, manage identity log-ins, handle push notifications, and run custom code in the cloud. Oracle has created an adapter to take advantage of all the features of the Oracle converged database while using Parse APIs. 1. Installing the Oracle Backend for Parse Platform The first step involves installing the Oracle Backend for Parse Platform. This can be found in Oracle Cloud Marketplace. After installing, you will have a Parse Server URI that the mobile app will use to access. The Backend/Platform is very convenient, as it automatically provisions and configures the Parse Server and Oracle Database that it uses as well as a Kubernetes cluster that the Parse Server runs on, and thus, also providing scaling, microservice, observability, etc., capabilities. 2. Installing Unity and Creating a Project The second step involves installing Unity and creating a project. Unity Hub can be installed from here and a new project can be created by clicking the "New Project" button. I used the "2D Mobile (Core)" project template. I used version 2021.2.21f1 of Unity; however, any number of versions from at least 2018 to current versions of Unity should work, as the Parse library is compatible across many versions as are the Unity resources used for the game. 3. Clone or Download Unity Package Next, clone or download the Unity package containing the game from the GitHub repos. You can simply drag and drop the contents of this repos into the Assets directory of your Unity project and proceed to the "Looking Closer at the Unity Project" section. If interested in how Parse .NET SDK is added to the Unity project you can read the following section. There are two very simple steps involved in adding Parse .NET SDK to a Unity Project. First, add a link.xml file to the Assets directory in the Unity project. This instructs Unity to preserve/include the Parse library in the mobile application build. The link.xml file is included in the repos mentioned and looks like this: <linker> <assembly fullname="UnityEngine"> <type fullname="UnityEngine.iOS.NotificationServices" preserve="all"/> <type fullname="UnityEngine.iOS.RemoteNotification" preserve="all"/> <type fullname="UnityEngine.AndroidJavaClass" preserve="all"/> <type fullname="UnityEngine.AndroidJavaObject" preserve="all"/> </assembly> <assembly fullname="Parse.Unity"> <namespace fullname="Parse" preserve="all"/> <namespace fullname="Parse.Internal" preserve="all"/> </assembly> </linker> 2. Second, add the Parse libraries themselves. This can be done in one of two ways: Download the lib. Simply download and extract the package to obtain the parse.dll file and drop it somewhere under the Assets directory of your Unity project. The directory does not matter as far as functionality; however, the convention in Unity is to use a Plugins directory, i.e., place it at Assets/Plugins/Parse/parse.dll. Note that this is the latest: 2.0.0-develop-1 DLL version. I've included the same in the GitHub repos for convenience. Clone or download the src code (again, this is the latest/master branch version of the library) and drop it somewhere under the Assets directory of your Unity project. As before, the directory does not matter as far as functionality, but of course, a logical location is advised. Using this approach the Parse library will build as part of your Unity project. This is one of the nice advantages of Parse being open source as you can look and step into the code to understand it or even make contributions to a very welcoming community. Note that there is some useful information for using Parse from Unity in the documentation; however, it is based on Parse version 1.7.0 and an older Unity version. As stated above, I am using the latest 2.0.0-develop-1/master version. You can use either, but note that some of the API names have changed, etc. Looking Closer at the Unity Project Double-click ParsePaperScissorRockGame in the Scenes directory of the RockPaperScissorsParseGame project. Above on the left, we see the Hierarchy pane with various Unity GameObjects for the game including the buttons, pictures, and sounds. On the right, we see the Inspector pane with information and settings about the selected GameObject. In the case of the RPSGameObject, we see that a ParseRockPaperScissorsGame C# script is attached. All of the code for our game is in this one script. Unity allows the variables and actions in a script to be mapped to GameObjects visually: for example, the buttons that will trigger code, the images that will be displayed when the buttons are pushed, the sounds that will be played when a winner is announced as well as the text, etc. We can also set the Parse Server connection information as we see here (ApplicationID, ServerURI, etc.). The player name is built into the app itself but could just as easily be dynamically taken from a text field in the game's playboard interface the same way the opponent selection and object to throw are. In the middle is the Scene panel for visually designing the scene as well as the Game window where the game can be played and tested. Looking Closer at the Unity Script and Parse Code in ParseRockPaperScissorsGame.cs ParseRockPaperScissorsGame is a MonoBehaviour, which is the base class from which every Unity script derives. It contains a number of key lifecycle methods. We implement two of the most common in our game: Start and Update. The Start method is called only once when the script (or inherently as part of the GameObject it is attached to) is activated. In our Start method, we create the connection to the Parse Server and publicize() that ParseClient so that it is ready for use. We then populate the drop-down box of opponents with the results of a query to the RockPaperScissorsPlayers class (a class in Parse parlance being essentially a table or document). One of the great things about the Parse API is its concise simplicity as you can see in the ParseQuery API call here. ParseQuery<ParseObject> query = new ParseQuery<ParseObject>(client, ROCKPAPERSCISSORSPLAYERS_CLASS); var results = await query.FindAsync(); foreach (ParseObject obj in results) { opponentsDropDownList.Add(obj.Get<string>(PLAYERNAME)); The Parse Dashboard provides an easy-to-use GUI where we can see this data from RockPaperScissorsPlayers as well as the RockPaperScissors class that is used to store the moves made by opponents in the game. As mentioned and shown earlier, the buttons added in Unity for "rock", "paper", and "scissors" trigger corresponding methods in the script when clicked. Here we see the Rock() method. public void Rock() { Debug.Log("player throws rock"); rock.SetActive(true); insertPlayerThrow(ROCK); } The method sets the rock image to active so that it is visible to the player and calls the insertPlayerThrow method/function which does a SaveAsync/insert call on the RockPaperScissors class used to store opponents' moves. The ParseObject API is used to make such data modifications and there are also convenience API calls such as Increment() which are useful for keeping GameScores, etc. Finally, the variable isWaitOnReply is set to true, which is a value checked periodically by the Update() method, which we will discuss next. ParseObject psrGame = new ParseObject(ROCKPAPERSCISSORS_CLASS); psrGame[PLAYERNAME] = playerName; psrGame[OPPONENTNAME] = opponent; psrGame[PLAYERTHROW] = playerThrow; await psrGame.SaveAsync(); isWaitOnReply = true; The Update method is called by the Unity engine every frame and is used in our game to check for opponents' moves. Parse has different notification functionality; however, for this game, we are simply polling. Every frame is too frequent and so we have set the Update method to check every 3 seconds (the FixedUpdate method can also be used for this purpose, however, works slightly differently). The Update method calls the checkOpponent() method, which makes the following query to get the opponent's move (using WhereEqualTo calls to build the query), compare and announce a winner via text, and the activation of an appropriate sound (rock crushing scissors, paper wrapping rock, or scissors cutting paper). Finally, the opponent's move, having been processed, is issued a DeleteAsync/deleted. The actual player's move is not deleted at this point as the opponent may not have read it yet, and thus, each player is responsible for deleting their opponent's move for cleanup. (There is also a, hidden by default, "Delete All My Games" button in the Unity project and the corresponding method it calls to delete all games in case an opponent never checks.) ParseQuery<ParseObject> query = new ParseQuery<ParseObject>(client, ROCKPAPERSCISSORS_CLASS); query = query.WhereEqualTo(PLAYERNAME, opponent).WhereEqualTo(OPPONENTNAME, playerName); var results = await query.FindAsync(); foreach (ParseObject obj in results) { string opponentsMove = obj.Get<string>(PLAYERTHROW); Debug.Log("opponentsMove:" + opponentsMove); if (string.Equals(opponentsMove, "")) isWaitingOnReply = true; else { switch (opponentsMove) { case "rock": rockOpponent.SetActive(true); if (string.Equals(opponentsMove, playerThrow)) outcomeTextMesh.text = "Tie"; else if (string.Equals(playerThrow, PAPER)) { outcomeTextMesh.text = "You won!"; paperWinAudio.SetActive(true); } [...] await obj.DeleteAsync(); There is also special logic in the app for when "computer" is picked as the opponent but nothing particular to Unity or Parse and so the reader is referred to the source code for that. Build and Deploy the App Under File -> Build Settings, you can simply select the platform (e.g., Android or Apple) to build and run. Additional properties can be edited by clicking the Player Settings button. You can also simply hit play in the Unity Engine to test the app and play the game there. That's it. You can take it from there and build something beyond paper, scissors, or rock if you like. Access to the Oracle converged database makes for many interesting possibilities through all of its data types and features and you will have a powerful data and application backend supporting it with minimal effort so you can just focus on your application. Video A corresponding demonstration and explanation of the dev process for the game can also be viewed in the following video: Thanks for reading and let me know if you have any questions, feedback, or requests for other material and demos. I'm more than happy to hear from you.
In this video tutorial series, take a deeper look at the consumer group in Kafka with: 1 partition and 2 consumers 2 partitions and 2 consumers 3 partitions and 1 consumer Consumer Group in Kafka With 1 Partition and 2 Consumers In this first Apache Kafka tutorial, learn about Java Kafka consumer code with 1 partition and 2 consumers. Both consumers are registered to the same consumer group. One consumer will be sitting idle. The same partition cannot be assigned to multiple consumers in the same group. Let's begin! Consumer Group in Kafka With 2 Partitions and 2 Consumers The following tutorial explains the Kafka consumer group with 2 partitions and 2 consumers. Both consumers are registered to the same consumer group. The same partition cannot be assigned to multiple consumers in the same group. Consumer Group in Kafka with 3 Partitions and 1 Consumer Now let's learn about 3 partitions and 1 consumer within consumer groups in Apache Kafka. The message will be published to partition randomly. The message will be consumed by "consumer 1" in a round-robin. One consumer can consume messages from more than one partition.
Generics in Java In Java programming, language generics are introduced in J2SE 5 for dealing with type-safe objects. It detects bugs at the compile time by which code is made stable. Any object type is allowed to be stored in the collection before the generic introduction. Now after the generic introduction in the Java programming language, programmers are forced to store particular object types. Advantages of Java Generics Three main advantages of using generics are given below: 1. Type-Safety Generics allow to store of only a single object type. Therefore, different object types are not allowed to be stored in generics. Any type of object is allowed to be stored without generics. Java // declaring a list with the name dataList List dataList= new ArrayList(); // adding integer into the dataList dataList.add(10); // adding string data into the dataList dataList.add("10"); With generics, we need to tell the object type we want to store. Declaring a list with the name dataList: List<Integer> dataList= new ArrayList(); Adding integer into the dataList dataList.add(10); Adding string data into the dataList dataList.add("10"); // but this statement gives compile-time error 2. No Need for Type Casting Object type casting is not required with generics. It is required to do casting before a generic introduction. Java declaring a list with the name dataList List dataList= new ArrayList(); adding an element to the dataList dataList.add("hello"); typecasting String s = (String) dataList.get(0); There is no need for object type casting after generics. Java // declaring a list with the name dataList List<String> dataList= new ArrayList<String>(); // adding an element to the dataList dataList.add("hello"); //typecasting is not required String s = dataList.get(0); 3. Checking at Compile Time Issues will not occur at run time as it is checked at the compile time. And according to good programming strategies, problem handling done at compile time is far better than handling done at run time. Java // declaring a list with the name dataList List<String> dataList = new ArrayList<String>(); // adding an element into the dataList dataList .add("hello"); // try to add an integer in the dataList but this statement will give compile-time error dataList .add(32); Syntax: Java Generic collection can be used as : ClassOrInterface<Type> Example: An example of how generics are used in java is given below: ArrayList<String> Example Program of Java Generics The ArrayList class is used in this example. But in place of the ArrayList class, any class of collection framework can be used, like Comparator, HashMap, TreeSet, HashSet, LinkedList, ArrayList, etc. Java // importing packages import java.util.*; // creating a class with the name GenericsExample class GenericsExample { // main method public static void main(String args[]) { // declaring a list with the name dataList to store String elements ArrayList < String > dataList = new ArrayList < String > (); // adding an element into the dataList dataList.add("hina"); // adding an element into the dataList dataList.add("rina"); // if we try to add an integer into the dataList then it will give a compile-time error //dataList.add(32); //compile time error // accessing element from dataList String s = dataList.get(1); //no need of type casting // printing an element of the list System.out.println("element is: " + s); // for iterating over the dataList elements Iterator < String > itr = dataList.iterator(); // iterating and printing the elements of the list while (itr.hasNext()) { System.out.println(itr.next()); } } } Output: Java element is: rina hina rina Java Generics Example Using Map In this, we are using a map to demonstrate the generic example. The map allows the data storage in the form of key-value pairs. Java // importing packages import java.util.*; // creating a class with the name GenericsExample class GenericsExample { // main method public static void main(String args[]) { // declaring a map for storing keys of Integer type with String values Map < Integer, String > dataMap = new HashMap < Integer, String > (); // adding some key value into the dataMap dataMap.put(3, "seema"); dataMap.put(1, "hina"); dataMap.put(4, "rina"); // using dataMap.entrySet() Set < Map.Entry < Integer, String >> set = dataMap.entrySet(); // creating an iterator for iterating over the dataMap Iterator < Map.Entry < Integer, String >> itr = set.iterator(); // iterating for printing every key-value pair of map while (itr.hasNext()) { // type casting is not required Map.Entry e = itr.next(); System.out.println(e.getKey() + " " + e.getValue()); } } } Output: Java 1 hina 3 seema 4 rina Generic Class A generic class is a class that can refer to any type. And here, for creating a particular type of generic class, we are using the parameter of T type. The declaration of a generic class is much similar to the declaration of a non-generic class, except that the type parameter section is written after the name of the class. Parameters of one or more than one type are allowed in the type parameter section. A generic class declaration looks like a non-generic class declaration, except that the class name is followed by a type parameter section. As one or more parameters are accepted, so Parameterized types or parameterized classes are some other names for it. And the example is given below to demonstrate the use and creation of generic classes. Generic Class Creation Java class GenericClassExample < T > { T object; void addElement(T object) { this.object = object; } T get() { return object; } } Here T type represents that it can refer to any type, such as Employee, String, and Integer. The type specified by you for the class is used for data storage and retrieval. Generic Class Implementation Java Let us see an example for a better understanding of generic class usage // creating a class with the name GenericExample class GenericExample { // main method public static void main(String args[]) { // using the generic class created in the above example with the Integer type GenericClassExample < Integer > m = new GenericClassExample < Integer > (); // calling addElement for the m m.addElement(6); // if we try to call addElement with the string type element then it will give a compile-time error //m.addElement("hina"); //Compile time error System.out.println(m.get()); } } Output: Java 6 Generic Method Similar to the generic class, generic methods can also be created. And any type of argument can be accepted by the generic method. The declaration of the generic method is just similar to that of the generic type, but the scope of the type parameter is limited to the method where its declaration has been done. Generic methods are allowed to be both static and non-static. Let us understand the generic method of Java with an example. Here is an example of printing the elements of an array. Here E is used for representing elements. Java // creating a class with the name GenericExample public class GenericExample { // creating a generic method for printing the elements of an array public static < E > void printElements(E[] elements) { // iterating over elements for printing elements of an array for (E curElement: elements) { System.out.println(curElement); } System.out.println(); } // main method public static void main(String args[]) { // declaring an array having Integer type elements Integer[] arrayOfIntegerElements = { 10, 20, 30, 40, 50 }; // declaring an array having character-type elements Character[] arrayOfCharacterElements = { 'J', 'A', 'V', 'A', 'T', 'P', 'O', 'I', 'N', 'T' }; System.out.println("Printing an elements of an Integer Array"); // calling generic method printElements for integer array printElements(arrayOfIntegerElements); System.out.println("Printing an elements of an Character Array"); // calling generic method printElements for character array printElements(arrayOfCharacterElements); } } Output: Java Printing an elements of an Integer Array 10 20 30 40 50 Printing an elements of an Character Array J A V A T P O I N T Wildcard in Java Generics Wildcard elements in generics are represented by the question mark (?) symbol. And any type is represented by it. If <? extends Number> is written by us, then this means any Number such as (double, float, and Integer) child class. Now the number class method can be called from any of the child classes. Wildcards can be used as a type of local variable, return type, field, or Parameter. But the wildcards can not be used as type arguments for the invocation of the generic method or the creation of an instance of generic. Let us understand the wildcards in Java generics with the help of the example below given: Java // importing packages import java.util.*; // creating an abstract class with the name Animal abstract class Animal { // creating an abstract method with the name eat abstract void eat(); } // creating a class with the name Cat which inherits the Animal class class Cat extends Animal { void eat() { System.out.println("Cat can eat"); } } // creating a class with the name Dog which inherits the Animal class class Dog extends Animal { void eat() { System.out.println("Dog can eat"); } } // creating a class for testing the wildcards of java generics class GenericsExample { //creating a method by which only Animal child classes are accepted public static void animalEat(List << ? extends Animal > lists) { for (Animal a: lists) { //Animal class method calling by the instance of the child class a.eat(); } } // main method public static void main(String args[]) { // creating a list of type Cat List < Cat > list = new ArrayList < Cat > (); list.add(new Cat()); list.add(new Cat()); list.add(new Cat()); // creating a list of type Dog List < Dog > list1 = new ArrayList < Dog > (); list1.add(new Dog()); list1.add(new Dog()); // calling animalEat for list animalEat(list); // calling animalEat for list1 animalEat(list1); } } Output: Java Cat can eat Cat can eat Cat can eat Dog can eat Dog can eat Upper Bounded Wildcards The main objective of using upper-bounded wildcards is to reduce the variable restrictions. An unknown type is restricted by it to be a particular type or subtype of a particular type. Upper Bounded Wildcards are used by writing a question mark symbol, then extending the keyword if there is a class and implementing a keyword for the interface, and then the upper bound is written. Syntax of Upper Bound Wildcard ? extends Type. Example of Upper Bound Wildcard Let us understand the Upper Bound Wildcard with an example. Here upper bound wildcards are used by us for List<Double> and List<Integer> method writing. Java // importing packages import java.util.ArrayList; // creating a class with the name UpperBoundWildcardExample public class UpperBoundWildcardExample { // creating a method by using upper bounded wildcards private static Double sum(ArrayList << ? extends Number > list) { double add = 0.0; for (Number n: list) { add = add + n.doubleValue(); } return add; } // main method public static void main(String[] args) { // creating a list of integer type ArrayList < Integer > list1 = new ArrayList < Integer > (); // adding elements to the list1 list1.add(30); list1.add(40); // calling sum method for printing sum System.out.println("Sum is= " + sum(list1)); // creating a list of double type ArrayList < Double > list2 = new ArrayList < Double > (); list2.add(10.0); list2.add(20.0); // calling sum method for printing sum System.out.println("Sum is= " + sum(list2)); } } Output: Java Sum is= 70.0 Sum is= 30.0 Unbounded Wildcards Unknown type list is specified by the unbounded wildcards like List<?>. Example of Unbounded Wildcards Java // importing packages import java.util.Arrays; import java.util.List; // creating a class with the name UnboundedWildcardExample public class UnboundedWildcardExample { // creating a method displayElements by using Unbounded Wildcard public static void displayElements(List << ? > list) { for (Object n: list) { System.out.println(n); } } // main method public static void main(String[] args) { // creating a list of type integer List < Integer > list1 = Arrays.asList(6, 7, 8); System.out.println("printing the values of integer list"); // calling displayElements for list1 displayElements(list1); // creating a list of type string List < String > list2 = Arrays.asList("six", "seven", "eight"); System.out.println("printing the values of string list"); // calling displayElements for list2 displayElements(list2); } } Output: Java printing the values of integer list 6 7 8 printing the values of string list six seven eight Lower Bounded Wildcards Lower Bounded Wildcards are used to restrict the unknown type to be a particular type or the supertype of the particular type. Lower Bounded Wildcards are used by writing a question mark symbol followed by the keyword super, then writing the lower bound. Syntax of Lower Bound Wildcard ? super Type. Example of Lower Bound Wildcard Java // importing packages import java.util.*; // creating a class with the name LowerBoundWildcardExample public class LowerBoundWildcardExample { // creating a method by using upper bounded wildcards private static void displayElements(List << ? super Integer > list) { for (Object n: list) { System.out.println(n); } } // main method public static void main(String[] args) { // creating a list of type integer List < Integer > list1 = Arrays.asList(6, 7, 8); System.out.println("printing the values of integer list"); // calling displayElements for list1 displayElements(list1); // creating a list of type string List < Number > list2 = Arrays.asList(8.0, 9.8, 7.6); System.out.println("printing the values of string list"); // calling displayElements for list2 displayElements(list2); } } Output: Java printing the values of integer list 6 7 8 printing the values of string list 8.0 9.8 7.6 Conclusion After the generic introduction in the Java programming language, programmers are forced to store particular object types. Type safety, No need for type casting, and Checking at compile time are Three main advantages of using generics. A generic class is a class that can refer to any type. Similar to the generic class, generic methods can also be created. And any type of argument can be accepted by the generic method. Wildcard elements in generics are represented by the question mark (?) symbol. Upper Bounded, Lower Bounded, and Unbounded are three types of wildcards in Java generic.
Conditional statements are used for making decisions based on different conditions. Also, conditionals in JavaScript allow you to execute different blocks of code based on whether a certain condition is true or false. Conditions can be implemented using the following ways: If If Else Else If Switch Ternary operators If Statement This statement shows truthy values used to check if the given conditions are true and then execute the block of code. JavaScript if(condition){ -----block of code which is going to execute----- } Let's understand this with an example. JavaScript let num = 10 if (num > 0) { console.log(num + "is a positive number") } //Output => 10 is a positive number// //In the above example, we set the condition that if the user enters any number greater than 0, then "if" condition got executed and it returns the output.// Else Statement It's the opposite of an If statement. So we will say that, if the If condition is not executed, which will happen when the given condition is false, then the Else statement gets executed. JavaScript if(condition){ -----block of code which is going to execute----- } else { -----block of code which is going to execute----- } Let's take an example and try to understand this. JavaScript //lets say we made a google form for football trails and age limit for the peoples who can apply for this is 16+. Now, If the user enter age more or less than the given age, certain blocks of code gets executed and give response accordingly.// let myAge = 15 if (myAge > 16) { console.log(myAge + " is valid age, you are eligible for trials.") } else { console.log(myAge + " is not a valid age, you are not eligible for the trials .") } //I Hope this clears how "If" and "Else" statements works// Else If This is used for most cases because there are multiple options to select from sometimes. Let's understand this with an example. JavaScript if(condition){ -----block of code which is going to execute----- } else if(condition){ -----block of code which is going to execute----- } else { -----block of code which is going to execute----- } Let's say we found an interesting website, and in order to get the most out of this website, it's asking us to make an account on it. As we are going through the process of making an account, it asks us to set questions and answers in case we lose our password so we can still be able to log in by giving the correct answer to the questions. Now, a few months pass and we want to sign in to that website but we couldn't remember our password, so the website gives us the option to sign in by giving answers to the questions we set previously. It gives us a question and four options to choose from. Que: What is your favorite color? a) blue b) Indigo c) pink d) red JavaScript let favColor = 'blue' if(favColor === 'indigo'){ console.log("indigo is not your favorite color.Try again") } else if(favColor === 'pink'){ console.log("pink is not your favorite color.Try again") } else if(favColor === 'red'){ console.log("Seriously, red, broooo Try again") } else if(favColor === 'blue'){ console.log("Thats my bro, blue is your fav color. Never forget your password again.") } else { console.log("Enter your fav color") } Switch Statement The switch statement is an alternative for If Else statements. The switch statement makes the code more concise and easier to read when you need to test a single variable against multiple possible values. JavaScript switch (case value) { case 1: console.log(' ') break; //Suppose, the condition is satisfied after end of case 1, then, 'break' terminates the code// case 2: console.log(' ') break; default: //default runs only if all the cases dont satisfy conditions.// console.log(' ') } Let's understand this with an example. JavaScript let theDay = 'tuesday' switch(theDay) { case'monday': console.log('Today is not monday'); break; case'tuesday': console.log('Yes, today is tuesday'); break; default: console.log('Please enter a valid day'); } //In this example, the code terminates after 2nd case, as the condition is satisfied at case 2// Ternary Operator It is a simple way of writing an If Else statement. It takes three values or operands: A condition Expression to execute if the condition is true Expression to execute if the condition is false Let's understand this with an example. JavaScript let playFootball = true playFootball ? console.log('you needs football boots to play on ground') : console.log('i dont wanna play') The ternary operator is useful when you need to make a simple decision based on a single condition. It can make your code more concise and easier to read, especially when used with short expressions. Conclusion This blog discusses various conditional statements in JavaScript, which allow the execution of different blocks of code based on certain conditions. This includes If, Else, Else If, Switch, and Ternary operators. If statements are used to check whether a condition is true, while Else statements execute when the If condition is false. Else If statements are used when multiple options need to be considered. Switch statements are an alternative to If Else statements and make the code more concise. Ternary operators are a simple way of writing If Else statements.