This commit is contained in:
jamesfalkner
2019-06-21 16:37:44 -04:00
parent 174efdee6d
commit e843154da7
8 changed files with 383 additions and 18 deletions

1
.gitignore vendored
View File

@@ -28,3 +28,4 @@ nbdist/
.nb-gradle/
.DS_Store
*.secret

View File

@@ -17,6 +17,8 @@ modules:
name: Using Quarkus extensions
panache:
name: Hibernate ORM with Panache
openapi:
name: Documenting and Testing APIs
messaging:
name: Event-driven Messaging
kafka:
@@ -25,3 +27,5 @@ modules:
name: Monitoring with Prometheus and Grafana
tracing:
name: Tracing Quarkus Apps
security:
name: Securing Resources with Keycloak

View File

@@ -18,7 +18,9 @@ modules:
- cloudnative
- extensions
- panache
- openapi
- messaging
- kafka
- monitoring
- tracing
- security

View File

@@ -10,7 +10,7 @@ In Che, click on **Import Project...**. In the dialog box, select **GITHUB** as
[source,none,role="copypaste"]
----
https://github.com/jamesfalkner/summit-2019-devzone
https://github.com/RedHatWorkshops/quarkus-workshop-labs
----
image::imgs/import.png[]
@@ -28,7 +28,7 @@ image::imgs/structure.png[]
The project has
* The Maven structure
* An `org.acme.quickstart.GreetingResource` resource exposed on `/hello`
* An `org.acme.people.rest.GreetingResource` resource exposed on `/hello`, along with a simple test
* A landing page that is accessible on `http://localhost:8080` after starting the application
* The application configuration file
@@ -51,7 +51,7 @@ Double-click on `pom.xml` in the project browser to open it in the editor. You w
And a few more `<dependency>` imports and other ancillary sections. We will be adding things to our `pom.xml` in future sections.
Navigate to `src -> main -> java -> org.acme.quarkus.sample` in the project tree and double click on `GreetingResource.java`. This class has a very simple RESTful endpoint definition:
Navigate to `src -> main -> java -> org.acme.people.rest` in the project tree and double click on `GreetingResource.java`. This class has a very simple RESTful endpoint definition:
[source, java]
----
@@ -75,7 +75,7 @@ Compared to vanilla JAX-RS, with Quarkus there is no need to create an `Applicat
## Running the Application
Now we are ready to run our application. In Che, select the _Command Palette_ by clicking on its icon in the upper right, and double-click on **Run Locally**:
Now we are ready to run our application. In Che, select the _Command Palette_ by clicking on its icon in the upper right, and double-click on **Build and Run Locally**:
image::images/runlocally.png[]
@@ -103,9 +103,11 @@ Since our RESTful endpoint listens on the `/hello` endpoint, add `/hello` to the
You should see `hello` in your browser tab, which means its working!
image::images/hellopage.png[]
Now, let's exercise the **live reload** capabilities of Quarkus. In Che, edit the `GreetingResource.java` file and change `return "hello";` to `return "hola";` in the editor. Press `CTRL-S` (or `CMD-S` on Mac OS) to save the file. Don't recompile or restart anything. Just try to reload the same brower tab that was showing `hello`. It should now show `hola`.
Wow, how cool is that? Supersonic Subatomic live reload! Go ahead and change it a few more times and access the endpoint again. And we're just getting started.
Wow, how cool is that? Supersonic Subatomic live reload! Go ahead and change it a few more times and access the endpoint again. And we're just getting started. Leave the app running so we can continue to change it on the fly in the next section.
[NOTE]
====

View File

@@ -1 +1,65 @@
## The Basics
## Dependency Injection
In the previous step you created a basic RESTful Java application with Quarkus. In this step we'll add a custom bean that will use the _ArC_ extension which provides a CDI-based dependency injection https://quarkus.io/guides/cdi-reference.html[solution] tailored for the Quarkus architecture.
## Add Custom Bean
Lets modify the application and add a companion bean. In CodeReady, right-click on the `org.acme.people.service` package in the project browser and select _New_ --> _Java Class_. Name the class `GreetingService`, and then copy the below code into the class:
[source,java,role="copypaste"]
----
package org.acme.people.service;
import javax.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class GreetingService {
private String hostname = System.getenv().getOrDefault("HOSTNAME", "unknown");
public String greeting(String name) {
return "hello " + name + " from " + hostname;
}
}
----
This is an injectable bean that implements a `greeting()` method returning a string `hello <hostname>` (where `<hostname>` is the Linux hostname of the machine on which the code runs).
Next, open the existing `GreetingResource.java` class file and add a new field and method above the existing `hello` method:
[source,java,role="copypaste"]
----
@Inject
GreetingService service;
@GET
@Produces(MediaType.TEXT_PLAIN)
@Path("/greeting/{name}")
public String greeting(@PathParam("name") String name) {
return service.greeting(name);
}
----
This will cause our new `GreetingResource` class to be instantiated and injected as the `service` field, and then the method `greeting` accesses this service to return the name.
You may notice that you get red error squigglies when you paste this code due to missing import statements. There are two ways with Che to fix this. Either right-click on the offending statement, and choose **Quick Fix** and then choose to import the missing API, or use the _Assistant_ > _Organize Imports_ menu option to do them en masse. You may need to choose from multiple matches, e.g. make sure you choose to import `javax.ws.rs.PathParam` when fixing the missing `PathParam` import. In general, look for `javax` or other well-known names to import. If you get it wrong you'll find out soon enough.
## Inspect the results
Since we still have our app running frmo before, when you make these changes and reload the endpoint, Quarkus will notice all of these changes and live reload them.
Check that it works as expected by loading the new endpoint using the Preview URL and adding the `/hello/greeting/quarkus` to the end of the URL:
image::images/greetingpage.png[]
Note we are exercising our new bean using the `/hello/greeting` endpoint, and you should see `hello quarkus from <hostname>`.
[NOTE]
====
In this case, the hostname is the hostname from the node the app is running on within Kubernetes and will change later on.
====
## Congratulations!
It's a familiar CDI-based environment for you Enterprise Java developers out there, with powerful mechanisms to reload your code _as you type_ (or very close to realtime). In the next step, we'll create some tests for our app, which should also be familiar to _all_ developers.

View File

@@ -1 +1,279 @@
## The Basics
## Testing
In this step we'll show you how to effectively write functional and unit tests for your Quarkus Apps.
You should already have a completed test written for you, including the correct `pom.xml` setup where you should see 2 test dependencies:
[source, xml]
----
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<version>${quarkus.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
----
`quarkus-junit5` is required for testing, as it provides the `@QuarkusTest` annotation that controls the testing framework. `rest-assured` is not required but is a convenient way to test HTTP endpoints, we also provide integration that automatically sets the correct URL so no configuration is required.
The basic test is in the `GreetingResourceTest` (in the `src/test/java/org/acme/people` directory). For example:
[source, java]
----
@QuarkusTest
public class GreetingResourceTest {
@Test
public void testHelloEndpoint() {
given()
.when().get("/hello")
.then()
.statusCode(200)
.body(is("hello"));
}
----
[NOTE]
====
When creating new tests, always double-check to be sure you're in the `src/test` directory tree, not `src/main`!
====
## Add a new test
Add a new test in `GreetingResourceTest` that tests the `/hello/greeting` endpoint by copying this code below the existing test:
[source, java, role="copypaste"]
----
@Test
public void testGreetingEndpoint() {
String uuid = UUID.randomUUID().toString();
given()
.pathParam("name", uuid)
.when().get("/hello/greeting/{name}")
.then()
.statusCode(200)
.body(startsWith("hello " + uuid));
}
}
----
## Test the app
Let's test our app. In Che, using the same command palette, select **test**:
image::images/testcmd.png[]
The tests will run, and eventually complete. Did you get any errors? You probably got:
[source, none]
----
[ERROR] Failures:
[ERROR] GreetingResourceTest.testHelloEndpoint:18 1 expectation failed.
Response body doesn't match expectation.
Expected: is "hello"
Actual: hola
----
This is because you changed the greeting in an earlier step. In `GreetingResource`, change `hola` back to `hello` and re-run the test and confirm it passes with `BUILD SUCCESS`.
## Controlling the test port
While Quarkus will listen on port `8080` by default, when running tests it defaults to `8081`. This allows you to run tests while having the application running in parallel (which you just did).
You can configure the port used by tests by configuring `quarkus.http.test-port` in your `application.properties`. Open that file (it's in `src/main/resources`) and add a new line at the end:
[source, none, role="copypaste"]
----
quarkus.http.test-port=8083
----
Now re-run the tests and look for
[source, none]
----
INFO [io.quarkus] (main) Quarkus 0.17.0 started in 1.997s. Listening on: http://[::]:8083
----
## Injecting a URI
It is also possible to directly inject the URL into the test which can make is easy to use a different client. This is done via the `@TestHTTPResource` annotation.
Lets write a simple test that shows this off to load some static resources. First create a simple HTML file in `src/main/resources/META-INF/resources/`. Right-click on this directory and select _New -> HTML File_. Name the file `test` in the dialog box (which will cause the final filename to be `test.html`):
image::images/createhtml.png[]
Next, create a new test under `src/test/java` in the `org.acme.people` package called `StaticContentTest`. To do this, right-click on the `org.acme.people` package and select _New -> Java Class_. Name the class `StaticContentTest`. Replace the contents of the file with this code:
[source, java, role="copypaste"]
----
package org.acme.people;
@QuarkusTest
public class StaticContentTest {
@TestHTTPResource("test.html")
URL url;
@Test
public void testIndexHtml() throws Exception {
try (InputStream in = url.openStream()) {
String contents = readStream(in);
Assertions.assertTrue(contents.contains("<title>Testing with Quarkus</title>"));
}
}
private static String readStream(InputStream in) throws IOException {
byte[] data = new byte[1024];
int r;
ByteArrayOutputStream out = new ByteArrayOutputStream();
while ((r = in.read(data)) > 0) {
out.write(data, 0, r);
}
return new String(out.toByteArray(), StandardCharsets.UTF_8);
}
}
----
You'll need to _Assistant -> Optimize Imports_ to import the right classes. In the dialog box, pick the appropriate class to import, clicking _Next >_ until you've eliminated all the ambiguities, and click _Finish_. In particular, be sure to import `java.io.InputStream`, `java.io.ByteArrayOutputStream`, and `java.net.URL`.
The `@TestHTTPResource` annotation allows you to directly inject the URL of the Quarkus instance, the value of the annotation will be the path component of the URL. For now `@TestHTTPResource` allows you to inject URI, URL and String representations of the URL.
Re-run the tests to ensure they're still passing.
## Injection into tests
So far we have only covered integration style tests that test the app via HTTP endpoints, but what if we want to do unit testing and test our beans directly?
Quarkus supports this by allowing you to inject CDI beans into your tests via the `@Inject` annotation (in fact, tests in Quarkus are full CDI beans, so you can use all CDI functionality). Lets create a simple test that tests the greeting service directly without using HTTP. Create a new test class in `src/test` in the `org.acme.people` package called `GreetingServiceTest`. Use the following code for the file's contents:
[source, java, role="copypaste"]
----
package org.acme.people;
import javax.inject.Inject;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import io.quarkus.test.junit.QuarkusTest;
@QuarkusTest
public class GreetingServiceTest {
private static final Logger LOGGER = LoggerFactory.getLogger("GreetingServiceTest");
@Inject
GreetingService service;
@Test
public void testGreetingService() {
LOGGER.info("greeting: " + service.greeting("Quarkus"));
Assertions.assertTrue(service.greeting("Quarkus").startsWith("hello Quarkus"));
}
}
----
Here we are injecting our `GreetingService` and calling it, just as our RESTful resource endpoint does in the production code.
Again, use _Assistant -> Organize Imports_ to add any needed imports (such as `GreetingService`). Run the tests again to verify the new test passes.
[NOTE]
====
As mentioned above Quarkus tests are actually full CDI beans, and as such you can apply CDI interceptors as you would normally. As an example, if you want a test method to run within the context of a transaction you can simply apply the `@Transactional` annotation to the method and the transaction interceptor will handle it.
In addition to this you can also create your own test stereotypes. Stereotypes can be particularly useful in large applications where you have a number of beans that perform similar functions, as it allows you to do something akin to multiple inheritance (multiple annotations) without having to repeat yourself over and over.
For example we could create a `@TransactionalQuarkusTest` if we needed to write a large number of tests that required transactional support with particular configuration. It would look like:
[source, java]
----
@QuarkusTest
@Stereotype
@Transactional
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface TransactionalQuarkusTest {
}
----
If we then apply this annotation to a test class it will act as if we had applied both the `@QuarkusTest` and `@Transactional` annotations, e.g.:
[source, java]
----
@TransactionalQuarkusTest
public class TestStereotypeTestCase {
@Inject
UserTransaction userTransaction;
@Test
public void testUserTransaction() throws Exception {
Assertions.assertEquals(Status.STATUS_ACTIVE, userTransaction.getStatus());
}
}
----
====
## Mock support
Quarkus supports the use of mock objects using the CDI `@Alternative` mechanism. To use this simply override the bean you wish to mock with a class in the `src/test/java` directory, and put the `@Alternative` and `@Priority(1)`` annotations on the bean. Alternatively, a convenient `io.quarkus.test.Mock` stereotype annotation could be used. This built-in stereotype declares `@Alternative`, `@Priority(1)` and `@Dependent`.
Let's mock our existing `GreetingService`. Although our existing service is pretty simple, in the real world the service might have too many dependencies on external systems to be feasible to call directly.
Create a new class in `src/test/java` in the `org.acme.people` package called `MockGreetingService` with the following code:
[source, java, role="copypaste"]
----
package org.acme.people;
import javax.enterprise.context.ApplicationScoped;
import org.acme.people.service.GreetingService;
import io.quarkus.test.Mock;
@Mock
@ApplicationScoped
public class MockGreetingService extends GreetingService {
@Override
public String greeting(String name) {
return "hello " + name + " from mock greeting";
}
}
----
Now modify our existing `GreetingServiceTest` class to add a log statement showing the value retrieved during the test. Modify the `testGreetingService` method to look like:
[source, java, role="copypaste"]
----
private static final Logger LOGGER = LoggerFactory.getLogger("GreetingServiceTest");
@Test
public void testGreetingService() {
LOGGER.info("greeting: " + service.greeting("Quarkus"));
Assertions.assertTrue(service.greeting("Quarkus").startsWith("hello Quarkus"));
}
----
Basically we've added a new `LOGGER.info` line. Don't forget to _Assistant > Optimize Imports_ to fix any needed imports (be sure to import `org.slf4j.Logger` and `org.slf4j.LoggerFactory`).
Now run the tests again (using the command palette as usual), and watch the output closely - you will see:
[source, none]
----
INFO [GreetingServiceTest] (main) greeting: hello Quarkus from mock greeting
----
This confirms that our `MockGreetingService` is being used instead of the original `GreetingService`.
## Summary
In this section we covered basic testing of Quarkus Apps using the `@QuarkusTest` and supporting annotations. This is an important part of any software engineering project and with Quarkus, testing has never been easier. For more information on testing with Quarkus, be sure to review the https://quarkus.io/guides/getting-started-testing[Quarkus Testing Guide].
In the next section we'll talk about how to effectively debug Quarkus applications. On with the show!

View File

@@ -201,6 +201,9 @@ oc scale -n che deployment/che --replicas=1
# Add custom stack manually
# Import realm from
# https://raw.githubusercontent.com/quarkusio/quarkus-quickstarts/master/using-keycloak/config/quarkus-realm.json
# Scale the cluster
WORKERCOUNT=$(oc get nodes|grep worker | wc -l)
if [ "$WORKERCOUNT" -lt 10 ] ; then
@@ -210,7 +213,16 @@ if [ "$WORKERCOUNT" -lt 10 ] ; then
done
fi
# Install the strimzi operator for all namespaces
# Install the prometheus operator for all namespaces
# Pre-pull some images
# Build stack
# docker build --build-arg RH_USERNAME='YOURUSERNAME' --build-arg RH_PASSWORD='YOURPASSWORD' -t docker.io/schtool/che-quarkus-odo:j4k -f stack.Dockerfile .
# Put your credentials in rhsm.secret file to look like:
# RH_USERNAME=your-username
# RH_PASSWORD=your-password
#
# then:
# DOCKER_BUILDKIT=1 docker build --secret id=rhsm,src=rhsm.secret -t docker.io/username/che-quarkus-odo:latest -f stack.Dockerfile .
# docker push docker.io/username/che-quarkus-odo:latest

View File

@@ -1,7 +1,6 @@
FROM registry.access.redhat.com/codeready-workspaces/stacks-java-rhel8:1.2
# syntax = docker/dockerfile:experimental
ARG RH_USERNAME
ARG RH_PASSWORD
FROM registry.access.redhat.com/codeready-workspaces/stacks-java-rhel8:1.2
USER root
@@ -19,15 +18,18 @@ RUN tar xzf /tmp/mvn.tar.gz && rm -rf /tmp/mvn.tar.gz && mkdir /usr/local/maven
ENV PATH="/usr/local/maven/apache-maven-3.6.0/bin:${PATH}"
ENV MAVEN_OPTS="-Xmx1024M -Xss128M -XX:MetaspaceSize=512M -XX:MaxMetaspaceSize=1024M -XX:+CMSClassUnloadingEnabled"
RUN --mount=type=secret,id=rhsm username="$(grep RH_USERNAME /run/secrets/rhsm|cut -d= -f2)" && password="$(grep RH_PASSWORD /run/secrets/rhsm|cut -d= -f2)" && subscription-manager register --username $username --password $password --auto-attach && yum install -y gcc zlib-devel && yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm && yum install -y siege && subscription-manager remove --all && subscription-manager unregister
RUN subscription-manager register --username $RH_USERNAME --password $RH_PASSWORD --auto-attach && yum install -y gcc zlib-devel && yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm && yum install -y siege && subscription-manager remove --all && subscription-manager unregister
ENV MAVEN_OPTS="-Xmx4G -Xss128M -XX:MetaspaceSize=1G -XX:MaxMetaspaceSize=2G -XX:+CMSClassUnloadingEnabled"
RUN chown -R jboss /home/jboss/.m2
USER jboss
RUN cd /tmp && mkdir project && cd project && mvn io.quarkus:quarkus-maven-plugin:0.16.1:create -DprojectGroupId=org.acme -DprojectArtifactId=footest -Dextensions="reactive-kafka,vert.x" && mvn clean compile package && mvn clean compile package -Pnative && mvn clean && cd / && rm -rf /tmp/project
RUN cd /tmp && mkdir project && cd project && mvn io.quarkus:quarkus-maven-plugin:0.17.0:create -DprojectGroupId=org.acme -DprojectArtifactId=footest -Dextensions="quarkus-agroal,quarkus-arc,quarkus-hibernate-orm,quarkus-hibernate-orm-panache,quarkus-jdbc-h2,quarkus-jdbc-postgresql,quarkus-kubernetes,quarkus-scheduler,quarkus-smallrye-fault-tolerance,quarkus-smallrye-health,quarkus-smallrye-opentracing" && mvn clean compile package && mvn clean && cd / && rm -rf /tmp/project
RUN cd /tmp && mkdir project && cd project && mvn io.quarkus:quarkus-maven-plugin:0.17.0:create -DprojectGroupId=org.acme -DprojectArtifactId=footest -Dextensions="quarkus-smallrye-reactive-streams-operators,quarkus-smallrye-reactive-messaging,quarkus-smallrye-reactive-messaging-kafka,quarkus-swagger-ui,quarkus-vertx,quarkus-kafka-client, quarkus-smallrye-metrics,quarkus-smallrye-openapi" && mvn clean compile package && mvn clean compile package -Pnative && mvn clean && cd / && rm -rf /tmp/project
RUN siege && sed -i 's/^connection = close/connection = keep-alive/' $HOME/.siege/siege.conf && sed -i 's/^benchmark = false/benchmark = true/' $HOME/.siege/siege.conf
RUN echo '-w "\n"' > $HOME/.curlrc
USER root
RUN chown -R jboss /home/jboss/.m2
RUN chmod -R a+w /home/jboss/.m2
USER jboss