Transcript
Page 1: Atmosphere 2014: JUnit: beyond the basics - Adam Dudczak

JUNITBeyond the basics

/ Adam Dudczak @maneo

Atmosphere 2014, Warszawa, 19-20.05.2014

Page 2: Atmosphere 2014: JUnit: beyond the basics - Adam Dudczak

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

Poznań JUGGeeCON

Page 3: Atmosphere 2014: JUnit: beyond the basics - Adam Dudczak

WHY JUNIT?

source: wikipedia

Page 4: Atmosphere 2014: JUnit: beyond the basics - Adam Dudczak

THERE IS NO SPOCK...

source: http://bit.ly/R0r8Ox

Page 5: Atmosphere 2014: JUnit: beyond the basics - Adam Dudczak

WHY NOT?

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

Page 6: Atmosphere 2014: JUnit: beyond the basics - Adam Dudczak

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); }}

Page 7: Atmosphere 2014: JUnit: beyond the basics - Adam Dudczak

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")); }}

Page 8: Atmosphere 2014: JUnit: beyond the basics - Adam Dudczak

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])); }}

Page 9: Atmosphere 2014: JUnit: beyond the basics - Adam Dudczak

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

Page 10: Atmosphere 2014: JUnit: beyond the basics - Adam Dudczak

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"},}); } ...

Page 11: Atmosphere 2014: JUnit: beyond the basics - Adam Dudczak

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;}...

Page 12: Atmosphere 2014: JUnit: beyond the basics - Adam Dudczak

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

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

Page 13: Atmosphere 2014: JUnit: beyond the basics - Adam Dudczak

LET'S BRAKE SOMETHING...

Two independent tests

A lot of boilerplate code!

Only one occurance of @Parameters per class

Page 14: Atmosphere 2014: JUnit: beyond the basics - Adam Dudczak

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>

Page 15: Atmosphere 2014: JUnit: beyond the basics - Adam Dudczak

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));}

Page 16: Atmosphere 2014: JUnit: beyond the basics - Adam Dudczak

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) { }

Page 17: Atmosphere 2014: JUnit: beyond the basics - Adam Dudczak

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) { }

Page 18: Atmosphere 2014: JUnit: beyond the basics - Adam Dudczak

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!!

Page 19: Atmosphere 2014: JUnit: beyond the basics - Adam Dudczak

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)

Page 20: Atmosphere 2014: JUnit: beyond the basics - Adam Dudczak

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

Page 21: Atmosphere 2014: JUnit: beyond the basics - Adam Dudczak

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

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

Page 22: Atmosphere 2014: JUnit: beyond the basics - Adam Dudczak

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);}

Page 23: Atmosphere 2014: JUnit: beyond the basics - Adam Dudczak

SOMETHING WENT WRONG!

@Seed("2300CE9BBBCFF4C8:573D00C2ABB4AD89")

Page 24: Atmosphere 2014: JUnit: beyond the basics - Adam Dudczak

@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

Page 25: Atmosphere 2014: JUnit: beyond the basics - Adam Dudczak

@RULE

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

Page 26: Atmosphere 2014: JUnit: beyond the basics - Adam Dudczak

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

ExternalResource, ExpectedException, TestName...

Page 27: Atmosphere 2014: JUnit: beyond the basics - Adam Dudczak

@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

Page 28: Atmosphere 2014: JUnit: beyond the basics - Adam Dudczak

@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() {

}}

Page 29: Atmosphere 2014: JUnit: beyond the basics - Adam Dudczak

@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 { .... }

Page 30: Atmosphere 2014: JUnit: beyond the basics - Adam Dudczak

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

Page 31: Atmosphere 2014: JUnit: beyond the basics - Adam Dudczak

JUNIT BENCHMARKSChart and persistent results history

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

Page 32: Atmosphere 2014: JUnit: beyond the basics - Adam Dudczak

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"); }}

Page 33: Atmosphere 2014: JUnit: beyond the basics - Adam Dudczak

"NEW" ASSERTIONS

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

Page 34: Atmosphere 2014: JUnit: beyond the basics - Adam Dudczak

"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"));

Page 35: Atmosphere 2014: JUnit: beyond the basics - Adam Dudczak

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);

Page 36: Atmosphere 2014: JUnit: beyond the basics - Adam Dudczak

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"); }; };}

Page 37: Atmosphere 2014: JUnit: beyond the basics - Adam Dudczak

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>

Page 38: Atmosphere 2014: JUnit: beyond the basics - Adam Dudczak

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

Page 39: Atmosphere 2014: JUnit: beyond the basics - Adam Dudczak

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 {}

...

Page 40: Atmosphere 2014: JUnit: beyond the basics - Adam Dudczak

CODE SAMPLESAll examples can be found at:

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

Page 41: Atmosphere 2014: JUnit: beyond the basics - Adam Dudczak

THAT'S ALL FOLKSThank you for your attention.

adam (at) dudczak.info / @maneo


Recommended