Showing posts with label JUnit 5. Show all posts
Showing posts with label JUnit 5. Show all posts

Wednesday, December 18, 2019

Creating a Coding Story (Coding Stories) with STS or Eclipse

CodingStories.io is a wonderful tool for senior developers to spread the knowledge of craftsmanship through an organization. Here is a good tool chain for doing this with STS/Eclipse:
  • Maven
  • JUnit 5
  • Git
Why Maven? Unless you want to assume all your Coding Stories readers will be using your IDE of choice (or let them work out setting up the dependencies), use Maven to abstract away your IDE for dependency management.
Why JUnit5? Because it's the most modern and is now well integrated into IDEs.
Why Git? Because that's what Coding Stories uses for integration.

Maven

Using STS's wizard, build a Maven project.



The below seems to be the simplest POM type.


Run the maven build and at should successfully build the Maven HelloWorld application.  (See troubleshooting if not.)

Rename the "project" folder per Coding Stories convention.  (I needed to add "-java" onto the end.) Later, this will be our Git repository.


The next section describes how to adjust the POM file and IDE to handle JUnit 5.

JUnit 5

Although IDEs have JUnit 5 integration, many of them default to JUnit 4. The following POM describes how to use JUnit 5 in your Maven project:

<project xmlns="http://maven.apache.org/POM/4.0.0"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

 <modelVersion>4.0.0</modelVersion>
 <groupId>biz.agilenoir</groupId>
 <artifactId>helloworldmocking</artifactId>

 <packaging>jar</packaging>
 <version>0.0.1-SNAPSHOT</version>
 <name>TDD going from Hello World to Mocking</name>
 <url>http://AgileNoir.biz</url>

 <dependencies>
  <!--JUnit tests depend on the engine artifact to build-->
  <dependency>
   <groupId>org.junit.jupiter</groupId>
   <artifactId>junit-jupiter-engine</artifactId>
   <version>5.5.2</version>
   <scope>test</scope>
  </dependency>
 </dependencies>

 <build>
  <plugins>
   <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.8.1</version>
    <!-- Maven by default doesn't support Java 13 so the below sets that up -->
    <configuration>
     <source>13</source>
     <target>13</target>
    </configuration>
   </plugin>
   <plugin>
   <artifactId>maven-surefire-plugin</artifactId>
   <version>2.22.2</version>
   </plugin>
  </plugins>
 </build>

</project>


What this does:

  • POM boiler plate: The first paragraph tells XML parsers about the schema to use for validation and declares the namespace.
  • project identity: your project information here
  • package the build as a Jar (required to package it into something)
  • dependencies: JUnit Jupiter is declared here
  • build/plugins: 
    • Maven plugin: 
      • Since I'm building with Java 13, I declare a modern version of the maven plugin to be used when building, 3.8.1 (you see, maven itself is a plugin).  The default maven plugin doesn't support newer versions of Java.
      • Configure the maven plugin to use Java 13
    • Surefire plugin: used to execute unit tests.
Configure the IDE's project to use JUnit 5:

 Add a simple JUnit 5 test to test the configuration and run it from Eclipse.



package biz.agilenoir.helloworldmocking;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
class HelloWorldTest {
@Test
void test() {
fail("Not yet implemented");
}
}
 Run the test and observe that it fails.




Run the test from Maven.  Watch the console for the test report.


If you want to see the test pass, make a change, then run the unit test.  All that's left is to configure Git for CodingStories.

By the way: regarding TDD

Check out the Agile Thoughts podcast.  This podcast gets the Confessions Of An Agile Coach's endorsement (naturally, since the same people produce it) of quality materials for developers and teams trying to get coding done in a way that avoids dealing with legacy issues such as bugs and hard to maintain code.  Agile Thoughts has a lot of great TDD conceptual content along with a radio drama about the difficulties to getting a TDD initiative started.  Click the podcast cover below or here for more information.



Configure Git

Using STS, create a Git repository inside your workspace (there is some controversy over whether this is the best use of Git, but as there is controversy and "no one right way" I think it's alright in this case.)




The project now has "badges" for M-maven, J-java, and a little gold storage icon for Git.

Write your Coding Story

Now that your environment is all tested out. It's time to build your coding story.  Follow "how to" at CodingStories to create your story. When you're finished, tell all your software developer friends and co-workers all about it.  Send them links.  Brag to your boss.  You know, socialize your tools to help others.

 XXX Link to my own coding story coming soon to here XXX

Tweet me if you get stuck: @LancerKind

References

https://howtodoinjava.com/junit5/junit5-maven-dependency/
https://maven.apache.org/plugins/maven-compiler-plugin/examples/set-compiler-source-and-target.html

Troubleshooting

Problem: Maven build gives me the following:

[ERROR] Source option 5 is no longer supported. Use 7 or later.
[ERROR] Target option 5 is no longer supported. Use 7 or later.
[INFO] 2 errors 
[INFO] -------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE

Solution:  Maven defaults to Java 1.6 and will default to a version of the maven plugin that won't work with java 9 or higher unless changes are made in "configuration" (https://maven.apache.org/guides/getting-started/maven-in-five-minutes.html).


 Problem: Can't execute unit test in STS


Solution: Click "OK" and setup a run configuration for JUnit 5.



Problem: "$ mvn test" (Maven) doesn't execute find my unit tests.

Solution: Use the Surfire plugin.  See it as the second plugin declared below.



Tuesday, May 24, 2016

Exceptional Exception Testing with JUnit 4.11 Rules and Java 8/JUnit5

Zipporah taking exception to poor 'ol Babbage


You've got something that takes exception to something else and now you want to automate a test to confirm it correctly throws per your specification:




 public class ProblemCauser {
    public void throwsException() {
        throw new IllegalArgumentException("Negative");
    }
}  
One way is the below. But can you see a problem?
import static org.junit.Assert.*;
import org.junit.Test;
public class RulesAndExceptionTest { 
 
    @Test(expected=IllegalArgumentException.class)
    public void canOnlyCheckTypeButNotMessage()
    {
        ProblemCauser problem = new ProblemCauser();
        problem.throwsException();       
    }
}
How do you know the exception message has been set to "Negative?" In the past, that left you with the following strategy:
    @Test
    public void theOldWayToTestExceptionMessageAndType() {
        try {
            ProblemCauser problem = new ProblemCauser();
            problem.throwsException();
            fail("didn't get expected exception");
        } catch (IllegalArgumentException expected) {
            assertEquals("Negative", expected.getMessage());
        }

    }
This does the job. Good work! You can actually fully test drive exception handling with this strategy.  The drawback is this is a lot of code to express a simple check. Even worse, many people forget to include the fail() call, which means if an exception doesn't happen, the test would pass.  This would be an incorrect test case. A better strategy is to use Junit 4.11's Rules package:
import static org.junit.Assert.*;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

public class RulesAndExceptionTest {
    @Rule
    public ExpectedException
exceptionPolicy = ExpectedException.none();  // Set the default expectation.
    @Test
    public void theNewWayToTestExceptionAndType() {
        exceptionPolicy.expect(IllegalArgumentException.class);
        exceptionPolicy.expectMessage("Negative");


        ProblemCauser problem = new ProblemCauser();
        problem.throwsException();
    }
}
 This is equivalent but uses less code. How does it work?

Exceptional with @Rules

 org.junit.Rule is an annotation that can be applied to a field or method. When JUnit executes the test class, it hands control to the Rule and decides how to execute Before, Test, and After, allowing the it to manage whatever it is supposed to manage. In the case of ExpectedException, allowing it to execute the @Test method and handle any exception thrown out to see if it's expected.

You can have multiple Rules in a test class doing various things. Here are other org.junit.rules.*(http://junit.org/junit4/javadoc/latest/org/junit/rules/package-summary.html):
Click image to zoom.
 Using JUnit's Rules, you can clean up exception handling code to one to three lines of code (one for the test class field, then one where you expect to get an exception) making it more readable and less error prone than using a try{...fail();}catch(Exception ex) { assertEquals(...);} pattern. More functionality with less code. That's exceptional!

Even MORE Exceptional with Java 8 and JUnit 5

JUnit 5 alpha was released in 2016 and it was designed to use Java 8 language features. assertThrows() tests if the exception type was correct. expectThrows() does the same AND returns the exception type so you can examine it further.
import static org.hamcrest.CoreMatchers.startsWith;
import static org.hamcrest.MatcherAssert.assertThat;
import org.junit.gen5.api.Assertions;
import org.junit.gen5.api.Test;
import org.junit.gen5.junit4.runner.JUnit5;
import org.junit.runner.RunWith;

@RunWith(JUnit5.class)
public class ExceptionTestingTest {
    @Test
    public void throwsExceptionWhenPopped() {
        ProblemCauser problem = new ProblemCauser();
        Throwable exception = Assertions.expectThrows(IllegalAccessException.class, () -> problem.throwsException());
        assertThat(exception.getMessage(), startsWith("Error:"));
    }
}
Java 8 and JUnit5 make exception testing even more exceptional. Java 8 makes it possible to pass function pointers. So now the function pointer to throwsException is passed into expectThrows. And because expect throws returns the Throwable, it's possible to check it's state. Notice the use of hamcrest's assertThat as JUnit5 doesn't have one. 
To get the Alpha 5.0 release to work with Eclipse's JUnit 4 plugin per: http://junit.org/junit5/docs/current/user-guide/#using-junit-4-to-run-junit-5-tests, I had to add the following dependencies to my build path (see Referenced Libraries). 
Notice JUnit 4 on the end.  This is for the @RunWith. If you have problems, scroll down to this article's Troubleshooting section. JUnit 5 and Open Test are available at Maven.org.

Other Solutions

Outside of JUnit 5, here are some other solutions but they seem "exceptional" as they require more code and dependencies than using JUnit4 Rules and the code doesn't seem more readable. Here are some articles:
http://blog.codeleak.pl/2014/07/junit-testing-exception-with-java-8-and-lambda-expressions.html 

For background on why you'd want to write micro tests, use your commute time to listen to the Agile Thoughts podcast series about the Test Automation Pyramid, episodes 1 through 8.  Each episode is short, to the point, and entertaining.
Episodes 1 through 8

References

http://junit.org/junit4/javadoc/latest/org/junit/Rule.html
http://junit.org/junit5/docs/current/user-guide/#migrating-from-junit4
http://junit.org/junit5/docs/current/user-guide/#using-junit-4-to-run-junit-5-tests
JUnit 5 API docs on assertThrows and expectThrows:  

Troubleshooting

JUnit5 with Eclipse JUnit 4 plugin test runner:
  •  ClassNotFound issue with DiscoveryFilter 
    • Solution: add junit-engine-api-5.0.0-ALPHA.jar to the build path.
  •  TestRunner executes but doesn't show any test being run. 
    • In the console, you see the following: org.junit.gen5.launcher.main.ServiceLoaderTestEngineRegistry loadTestEngines
      INFO: Discovered TestEngines with IDs [junit5]
      java.lang.NoClassDefFoundError: org/opentest4j/TestAbortedException
          at org.junit.gen5.engine.support.hierarchical.HierarchicalTestExecutor.(HierarchicalTestExecutor.java:37)
    • Solution: go to Maven repo and add OpenTest4J jar to the build path.