Atmosphere 2014: JUnit: beyond the basics - Adam Dudczak

Preview:

DESCRIPTION

Yes... I've heard about Spock ;-) and most likely dozens of other testing frameworks which were created after JUnit started to exist. JUnit is old, it's sometimes a bit grumpy, nevertheless it is stil very concise and lightweight library. It is enough to spend 10 minutes with JUnit documentation to understand the basic elements of test, how to write an assertion and how to launch your first test. In a day to day job there are several other JUnit features which might be very handy, there are also a few curosities worth to mention. In this presentation I will try to cover most of them including: parameterized tests, theories (org.junit.experimental.theories), rules (@Rule) and common matchers. As was already said JUnit is not a newbie in a testing framework world, over the years several additional libraries were added, I will show you a few interesting once, including randomized testing, benchmarking and REST interactions testing. This presentation was developed with the support of Poznań JUG, Tricity JUG and Szczecin JUG who were hosting this lecture in the past. Discussion which we had during and after the meetings was a great aid and influenced heavily current content of this presentation. Adam Dudczak - Adam Dudczak (@maneo), software engineer in Allegro group, working with Java (and JUnit) since 2004. Currently member of Allegro search team working on a better search experience in Allegro.pl. One of the leaders of Poznań JUG (www.jug.poznan.pl) and co-organizer of GeeCON conference (geecon.org). Father and husband, occasionally blogs at dudczak.info/dry.

Citation preview

JUNITBeyond the basics

/ Adam Dudczak @maneo

Atmosphere 2014, Warszawa, 19-20.05.2014

WHO AM I?Software Engineer in Allegro groupWorking with Java (and JUnit) since 2004One of the leaders of Co-organizer of conference

Poznań JUGGeeCON

WHY JUNIT?

source: wikipedia

THERE IS NO SPOCK...

source: http://bit.ly/R0r8Ox

WHY NOT?

source: http://bit.ly/1jNoT8f

ERRORRATE CLASSpublic class ErrorRate {

double errorRate;

public ErrorRate(long noOfItems, long noOfErrors) { errorRate = calculateErrorRate(noOfItems, noOfErrors); } double calculateErrorRate(long noOfItems, long noOfErrors) { ... }

public String getErrorRateAsString() { return String.format("%2.02f", errorRate); }}

SIMPLE ERRORRATE TESTpublic class ErrorRateTest {@Testpublic void shouldCalculateErrorRate() { //given ErrorRate errorRate = new ErrorRate(100, 10);

//when String result = errorRate.getErrorRateAsString();

//then assertThat(result, is("0.01")); }}

SIMPLE ERRORRATE TESTObject[][] testParameters = new Object[][]{ new Object[]{100,1,"0.01"}, new Object[]{0,0,"0.00"}, };

@Testpublic void shouldCalculateErrorRate1() { for (int i = 0; i>testParameters.length; i++) { ErrorRate errorRate = new ErrorRate((Integer)testParameters[i][0], (Integer)testParameters[i][1]); String result = errorRate.getErrorRateAsString(); assertThat(result, is((String)testParameters[i][2])); }}

LET'S BRAKE SOMETHING...Object[][] testParameters = new Object[][]{ new Object[]{ 100,1,"0.02"}, new Object []{0,0,"0.02"}, };

First error stops test

All cases are seen as one test

JUNIT PARAMETRIZED@RunWith(Parameterized.class)public class ErrorRateTest {

@Parameters public static Collection<Object[]> data() { return Arrays.asList(new Object[][]{ new Object[]{100,1,"0.01"}, new Object[]{0,0,"0.00"},}); } ...

JUNIT PARAMETRIZED (2)...long totalNumberOfItems;long totalNumberOfRejected;double finalErrorRate;

public ErrorRateTest(long totalNumberOfItems, long totalNumberOfRejected, double finalErrorRate) {

this.totalNumberOfItems = totalNumberOfItems; this.totalNumberOfRejected = totalNumberOfRejected; this.finalErrorRate = finalErrorRate;}...

JUNIT PARAMETRIZED (3)...@Testpublic void shouldCalculateErrorRate() { //given ErrorRate errorRate = new ErrorRate(totalNumberOfItems, totalNumberOfRejected); //when String result = errorRate.getErrorRateAsString();

//then assertThat(result, is(finalErrorRate));}...

LET'S BRAKE SOMETHING...

Two independent tests

A lot of boilerplate code!

Only one occurance of @Parameters per class

JUNIT-PARAMSParameterised tests that don't suck

Created by Paweł LipińskiA lot of very interesting ideas.

https://code.google.com/p/junitparams/

<dependency> <groupId>pl.pragmatists</groupId> <artifactId>JUnitParams</artifactId> <version>1.0.2</version> <scope>test</scope></dependency>

JUNIT-PARAMS - SHOWCASE@RunWith(JUnitParamsRunner.class)public class Samples_of_Usage_Test {

@Test@Parameters({"AAA,1", "BBB,2"})public void params_in_annotation(String p1, Integer p2) { }

@Test@Parameterspublic void params_in_default_method(String p1, Integer p2) { }

private Object parametersForParams_in_default_method(){ return $($("AAA", 1), $("BBB", 2));}

JUNIT-PARAMS - SHOWCASE@Test@Parameters(method = "named2,named3")public void params_in_multiple_methods(String p1, Integer p2) { } private Object named2() { return $($("AAA", 1)); } private Object named3() { return $($("BBB", 2)); }

@Test@Parameters(source = ParametersProvidersTest.OneIntegerProvider.class)public void parameters_from_external_class(int integer) { }

JUNIT-PARAMS - SHOWCASE@Test@FileParameters("src/test/resources/test.csv")public void load_params_from_csv(int age, String name) { }

@Test@FileParameters(value = "src/test/resources/test.csv", mapper = PersonMapper.class)public void load_params_from_any_file(PersonTest.Person person) { }

@Test@FileParameters("classpath:test.csv")public void load_params_from_classpath(int age, String name) { }

JUNIT-PARAMSMore examples can be found at iSamples_of_Usage_Test.java

PersonTest.java

Similar to approach in TestNG

Test code is clear and concise - YEAH!!

ORG.JUNIT.EXPERIMENTAL.THEORIESTheory is, in fact a parameterized testThis approach is more focused on requirements than onparticular test casesSimilar to ScalaCheck (Scala) / QuickCheck (Erlang)

ORG.JUNIT.EXPERIMENTAL.THEORIESTheory describes features of class/method and verifies themusing given set of input dataTheory is executed as one testIf assertion fails for any set of input data whole theory failsExample in ErrorRate_05_Theory_Test.java

RANDOMIZED UNIT TESTING"Monkey testing" - more randomness in your tests

http://labs.carrotsearch.com/randomizedtesting.html

RANDOMIZED UNIT TESTING@Testpublic void randomizedTesting() { // Here we pick two positive integers. // Note superclass utility methods. int a = randomIntBetween(0, Integer.MAX_VALUE); int b = randomIntBetween(0, Integer.MAX_VALUE); int result = Adder.add(a, b); assertTrue(result + " < (" + a + " or " + b + ")?", result >= a && result >= b);}

SOMETHING WENT WRONG!

@Seed("2300CE9BBBCFF4C8:573D00C2ABB4AD89")

@THREADLEAKING*Randomized Unit Testing library helps to check/controlthreads activity@ThreadLeaking* annotations verfies if threads are leakingfrom your tests/suiteCheck out ErrorRate_06_Randomized_Test.java

@RULE

source: http://bit.ly/1jNpE13

@RULEResuable @Before/@After... and moreExample in JunitRulesShowcaseTestJUnit has several built-in @Rules, ex.:

ExternalResource, ExpectedException, TestName...

@RULEpublic static class HasTempFolder { @Rule public TemporaryFolder folder = new TemporaryFolder();

@Test public void testUsingTempFolder() throws IOException { File createdFile = folder.newFile("myfile.txt"); File createdFolder = folder.newFolder("subfolder"); // ... }}

@Rule cannot be applied to static fields - use @ClassRule

@RULE IN BETAMAXimport co.freeside.betamax.Betamax;import co.freeside.betamax.Recorder;import org.junit.*;

public class MyTest {

@Rule public Recorder recorder = new Recorder();

@Betamax(tape="my tape") @Test public void testMethodThatAccessesExternalWebService() {

}}

@RULE WITH SPRING AND WIREMOCK@Rule public WireMockRule wireMockRule = new WireMockRule(8089); //Check out http://wiremock.org/

@Rule public TestRule contextRule = new SpringContextRule( new String[] { "testContext.xml" }, this);

@Autowired public String bar;

@Test public void testBar() throws Exception { .... }

JUNIT BENCHMARKS AND TIMEOUTpublic class MyTest { @Rule public TestRule benchmarkRun = new BenchmarkRule(); @Rule public TestTimeout timeoutRule = new TestTimeout(30); @Test public void twentyMillis() throws Exception { Thread.sleep(20); }}

MyTest.twentyMillis: [measured 10 out of 15 rounds]round: 0.02 [+- 0.00], round.gc: 0.00 [+- 0.00], GC.calls: 0, GC.time: 0.00, time.total: 0.32, time.warmup: 0.12, time.bench: 0.20

JUNIT BENCHMARKSChart and persistent results history

http://labs.carrotsearch.com/junit-benchmarks.html

BETTER EXCEPTION HANDLINGExample by Rafał Borowiec ( )blog.codeleak.pl/

public class ExpectedExceptionsTest {

@Rule public ExpectedException thrown = ExpectedException.none();

@Test public void verifiesTypeAndMessage() {

thrown.expect(RuntimeException.class); thrown.expectMessage("Runtime exception occurred");

throw new RuntimeException("Runtime exception occurred"); }}

"NEW" ASSERTIONS

source: http://bit.ly/1taOW0H

"NEW" ASSERTIONSassertThat and built-in Hamcrest matchersReadable assertions and better error handling

assertThat("this string", is("this string")); assertThat(theBiscuit, is(equalTo(myBiscuit))); assertThat("this string", containsString("is"));

ALTERNATIVE APPROACH - FEST/ASSERTJAlternative (but in fact a mainstream) way of buildingassertions

gives access to hundreds of assertionsAssertJassertThat(frodo.getName()).isEqualTo("Frodo");assertThat(frodo).isNotEqualTo(sauron) .isIn(fellowshipOfTheRing);assertThat(sauron).isNotIn(fellowshipOfTheRing);

ORGANIZE TESTS IN @SUITE@RunWith(Suite.class)@SuiteClasses({ ErrorRate_01_SimpleTest.class, ErrorRate_03_Parametrized_Test.class})public class SuiteInitializationExample {

@ClassRule public static ExternalResource resource= new ExternalResource() { @Override protected void before() throws Throwable { System.out.println("Starting the heavyweightServer"); };

@Override protected void after() { System.out.println("Stopping the heavyweightServer"); }; };}

CATEGORIES/SUITES AND BUILD TOOLSSuite is a bit Ant-ish - use CategoriesCategories are supported by both and Maven Gradle

<build> <plugins> <plugin> <artifactId>maven-surefire-plugin</artifactId> <configuration> <groups>com.ex.FastTests,com.ex.RegressionTests</groups> </configuration> </plugin> </plugins></build>

USE CATEGORIES ON THE @SUITE LEVELpublic class HeavyIntegrationTest { @Test @Category(HeavyWeight.class) public void shouldCalculateErrorRate() { assumeTrue(isHeavyWeightServerRunning()); //heave stuff with heavyWeight server here } ...

USE CATEGORIES ON THE @SUITE LEVEL@RunWith(Categories.class)@IncludeCategory(HeavyWeight.class)@SuiteClasses( { ErrorRate_01_SimpleTest.class, HeavyIntegrationTest.class})public class SuiteWithCategories {

//category marker interface public interface HeavyWeight {}

...

CODE SAMPLESAll examples can be found at:

https://bitbucket.org/maneo/junit-presentation/

THAT'S ALL FOLKSThank you for your attention.

adam (at) dudczak.info / @maneo

Recommended