SlideShare une entreprise Scribd logo
1  sur  99
Télécharger pour lire hors ligne
Bad Tests, Good Tests

Tomek Kaczanowski
http://twitter.com/#!/devops_borat
Tomek Kaczanowski

• Developer
• Team lead
• Blogger
   • http://kaczanowscy.pl/tomek
• Book author
   • http://practicalunittesting.com

• Working at CodeWise (Krakow)
    ...we are hiring, wanna join us?
Before we begin

• Most of the examples are real but:
    Obfuscated
       − to protect the innocents
     Truncated
       − imagine much more complex domain objects
• Asking questions is allowed
    ...but being smarter than me is not ;)
A little bit of history
Please...
                                           no more...




http://thedogatemycareplan.wordpress.com
Before we begin

• The tests were written in 2004-2006.
• No automation, no CI.
• Some tests do not compile.
• In some tests you can read a comment that "WARNING:
  test requires the divide param to be set to 20" but the
  code is so ugly, that there is no way to inject this value.
• Some test data are available in form of serialized objects
  (*.ser) that can not be currently deserialized, because
  the classes have changed.
• The project is now in maintenance.

                         Courtesy of Bartosz http://twitter.com/#!/bocytko
We don't need no stinkin' asserts!
public void testAddChunks() {
        System.out.println("*************************************");
        System.out.println("testAddChunks() ... ");
        ChunkMap cm = new ChunkMap(3);
        cm.addChunk(new Chunk("chunk"));

        List testList = cm.getChunks("chunk",null);

        if (testList.isEmpty())
            fail("there should be at least one list!");
        Chunk chunk = cm.getActualChunk("chunk",null);
        if (chunk.getElements().isEmpty())
            fail("there should be at least one element!");
        if (cm.getFinalChunkNr() != 1)
            fail("there should be at least one chunk!");
        // iterate actual chunk
        for (Iterator it = chunk.getElements().iterator();
                    it.hasNext();) {
            Element element = (Element) it.next();
            System.out.println("Element: " + element);
        }
        showChunks(cm);
        System.out.println("testAddChunks() OK ");
}
Success is not an option...

  /**
    * Method testFailure.
    */
  public void testFailure() {
       try {
           Message message = new Message(null,true);
           fail();
       } catch(Exception ex) {
           ExceptionHandler.log(ExceptionLevel.ANY,ex);
           fail();
       }
  }
What has happened? Well, it failed...
public void testSimple() {
        IData data = null;
        IFormat format = null;
        LinkedList<String> attr = new LinkedList<String>();
        attr.add("A");
        attr.add("B");

        try {
            format = new SimpleFormat("A");
            data.setAmount(Amount.TEN);
            data.setAttributes(attr);
            IResult result = format.execute();
            System.out.println(result.size());
            Iterator iter = result.iterator();
            while (iter.hasNext()) {
            IResult r = (IResult) iter.next();
               System.out.println(r.getMessage());
            ...
        }
        catch (Exception e) {
            fail();
        }
}
What has happened? Well, it failed...
public void testSimple() {
        IData data = null;
        IFormat format = null;
        LinkedList<String> attr = new LinkedList<String>();
        attr.add("A");
        attr.add("B");                       data is still null here.
                                                  Ready or not, NPE is coming.
         try {
             format = new SimpleFormat("A");
             data.setAmount(Amount.TEN);
             data.setAttributes(attr);
             IResult result = format.execute();
             System.out.println(result.size());
             Iterator iter = result.iterator();
             while (iter.hasNext()) {
             IResult r = (IResult) iter.next();
                System.out.println(r.getMessage());
             ...
         }
     catch (Exception e) {
             fail();
         }
}
Talk to me
//wait for messages
do {
    input = "";
    try {
        System.out.print(">");
        read = System.in.read(buf);
        //convert characters to string
        input = new String(buf, 0, read - newline.length());
        System.out.println(input);

        if (input.equals("end") || input.equals("exit")
            || input.equals("stop") || input.equals("quit")) {
                System.out.println("Terminating Test please wait...");
                System.out.println("******* Test terminated *******");
                toStop = true;
        }
        else {
            System.out.println("Commands:" + newline + "'end',
            'exit', 'stop' or 'quit' terminates this test ");
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
} while (!toStop);
Tests are boring – let us autogenerate them!
/**                                                          protected void tearDown() throws Exception {
* Generated by JUnitDoclet, a tool provided by                 // JUnitDoclet begin method testcase.tearDown
* ObjectFab GmbH under LGPL.                                   adapter = null;
* Please see www.junitdoclet.org, www.gnu.org                  super.tearDown();
* and www.objectfab.de for informations about                  // JUnitDoclet end method testcase.tearDown
* the tool, the licence and the authors.
*/                                                           public void testMain() throws Exception {
public class AdapterTest                                       // JUnitDoclet begin method testMain
// JUnitDoclet begin extends_implements                        Adapter.main(new String [] {"ADAPTER"});
extends TestCase                                               // JUnitDoclet end method testMain
// JUnitDoclet end extends_implements                        }
{
   // JUnitDoclet begin class
   Adapter adapter = null;                                   /**
   // JUnitDoclet end class                                  * JUnitDoclet moves marker to this method, if there is not match
                                                             * for them in the regenerated code and if the marker is not empty.
 public AdapterTest(String name) {                           * This way, no test gets lost when regenerating after renaming.
   // JUnitDoclet begin method AdapterTest                   * Method testVault is supposed to be empty.
   super(name);                                              */
   // JUnitDoclet end method AdapterTest                     public void testVault() throws Exception {
 }                                                             // JUnitDoclet begin method testcase.testVault
                                                               // JUnitDoclet end method testcase.testVault
 public Adapter createInstance() throws Exception {          }
   // JUnitDoclet begin method testcase.createInstance
   return new Adapter();                                     public static void main(String[] args) {
   // JUnitDoclet end method testcase.createInstance           // JUnitDoclet begin method testcase.main
 }                                                             junit.textui.TestRunner.run(AdapterTest.class);
                                                               // JUnitDoclet end method testcase.main
 protected void setUp() throws Exception {                   }
   // JUnitDoclet begin method testcase.setUp            }
   super.setUp();
   adapter = createInstance();
   // JUnitDoclet end method testcase.setUp
 }
Tests are boring – let us autogenerate them!
public void testSetGetTimestamp() throws Exception {
        // JUnitDoclet begin method setTimestamp getTimestamp
    java.util.Calendar[] tests = {new GregorianCalendar(), null};

        for (int i = 0; i < tests.length; i++) {
            adapter.setTimestamp(tests[i]);
            assertEquals(tests[i], adapter.getTimestamp());
        }
        // JUnitDoclet end method setTimestamp getTimestamp
    }

public void testSetGetParam() throws Exception {
    // JUnitDoclet begin method setParam getParam
    String[] tests = {"a", "aaa", "---", "23121313", "", null};

        for (int i = 0; i < tests.length; i++) {
            adapter.setParam(tests[i]);
            assertEquals(tests[i], adapter.getParam());
        }
        // JUnitDoclet end method setParam getParam
}
Bartosz, do you really work with such code?!
Conclusions

• Automation!
   • Running
   • Verification
• Tests are to be written not generated
• You should be informed why your test failed
• Master your tools
    …at least learn the basics!
Few words about tests
Why bother with tests?


• System works as expected



• Changes do not hurt



• Documentation



                             http://twitter.com/#!/devops_borat
Tests help to achieve quality




                     Not sure when I saw this picture –
                     probably in GOOS?
What happens if we do it wrong?
• Angry clients
• Depressed developers




                         http://www.joshcanhelp.com
When I started out with unit tests, I was
enthralled with the promise of ease and
security that they would bring to my
projects. In practice, however, the
theory of sustainable software through
unit tests started to break down. This
difficulty continued to build up, until I
finally threw my head back in anger and
declared that "Unit Tests have become
more trouble than they are worth."
               Llewellyn Falco and Michael Kennedy, Develop Mentor August 09
http://chrispiascik.com/daily-drawings/express-yourself/
It is a full-time job
No smoke without tests
class SystemAdminSmokeTest extends GroovyTestCase {

void testSmoke() {

    def ds = new org.h2.jdbcx.JdbcDataSource(
        URL: 'jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;MODE=Oracle',
        user: 'sa', password: '')

       def jpaProperties = new Properties()
       jpaProperties.setProperty(
           'hibernate.cache.use_second_level_cache', 'false')
       jpaProperties.setProperty(
           'hibernate.cache.use_query_cache', 'false')

       def emf = new LocalContainerEntityManagerFactoryBean(
          dataSource: ds, persistenceUnitName: 'my-domain',
          jpaVendorAdapter: new HibernateJpaVendorAdapter(
              database: Database.H2, showSql: true,
              generateDdl: true), jpaProperties: jpaProperties)

}
No smoke without tests
class SystemAdminSmokeTest extends GroovyTestCase {

void testSmoke() {
// do not remove below code
// def ds = new org.h2.jdbcx.JdbcDataSource(
//     URL: 'jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;MODE=Oracle',
//     user: 'sa', password: '')
//
//     def jpaProperties = new Properties()
//     jpaProperties.setProperty(
//         'hibernate.cache.use_second_level_cache', 'false')
//     jpaProperties.setProperty(
//         'hibernate.cache.use_query_cache', 'false')
//
//     def emf = new LocalContainerEntityManagerFactoryBean(
//        dataSource: ds, persistenceUnitName: 'my-domain',
//        jpaVendorAdapter: new HibernateJpaVendorAdapter(
//            database: Database.H2, showSql: true,
//            generateDdl: true), jpaProperties: jpaProperties)

…some more code below, all commented out :(
}
Let's follow the leader!

                  @Test
                  public class ExampleTest {

                        public void testExample() {
                           assertTrue(true);
                        }
                  }
Uh-oh, I feel lonely...

                   @Test
                   public class ExampleTest {

                          public void testExample() {
                             assertTrue(true);
                          }
                   }
Flickering tests
Asking for troubles...
LoggingPropertyConfigurator configurator
        = mock(LoggingPropertyConfigurator.class);
BaseServletContextListener baseServletContextListener
        = new BaseServletContextListener(configurator);

                                                          Should load some
@Test public void shouldLoadConfigProperties() {          default config
        baseServletContextListener.contextInitialized();
        verify(configurator).configure(any(Properties.class));
}


@Test(expected = LoggingInitialisationException.class)
                                                            Should load this
public void shouldThrowLoggingException() {                 specific file
        System.setProperty("logConfig", "nonExistingFile");
        baseServletContextListener.contextInitialized();
}
Lets mock!
Mock'em All!
public String getUrl(User user, String timestamp) {
         String name=user.getFullName();
         String url=baseUrl
             +"name="+URLEncoder.encode(name, "UTF-8")
             +"&timestamp="+timestamp;
         return url;
}

public String getUrl(User user) {
         Date date=new Date();
         Long time=(date.getTime()/1000); //convert ms to seconds
         String timestamp=time.toString();
         return getUrl(user, timestamp);
}
Mock'em All!
public String getUrl(User user, String timestamp) {
         String name=user.getFullName();
         String url=baseUrl
             +"name="+URLEncoder.encode(name, "UTF-8")
             +"&timestamp="+timestamp;
         return url;
}

public String getUrl(User user) {
         Date date=new Date();
         Long time=(date.getTime()/1000); //convert ms to seconds
         String timestamp=time.toString();
         return getUrl(user, timestamp);
}

                       @Test
                       public void shouldUseTimestampMethod() {
                                //given
                                Util spyUtil = spy(util);

                               //when
                               spyUtil.getUrl(user);

                               //then
                               verify(spyUtil).getUrl(eq(user), anyString());
                       }
Use the front door

@Test
public void shouldAddTimestampToGeneratedUrl() {
        //given
        util = new ....
        TimeProvider timeProvider = mock(TimeProvider.class);
        when(timeProvider.getTime()).thenReturn("12345");
        util.set(timeProvider);


        //when
        String url = util.getUrl(user);


        //then
        assertThat(url).contains("timestamp=12345");
}
Mock'em All!
@Test
public void shouldAddTimeZoneToModelAndView() {
    //given
    UserFacade userFacade = mock(UserFacade.class);
    ModelAndView modelAndView = mock(ModelAndView.class);
    given(userFacade.getTimezone()).willReturn("timezone X");


    //when
    new UserDataInterceptor(userFacade)
        .postHandle(null, null, null, modelAndView);


    //then
    verify(modelAndView).addObject("timezone", "timezone X");
}
Use the front door
@Test
public void shouldAddTimeZoneToModelAndView() {
    //given
    UserFacade userFacade = mock(UserFacade.class);
    ModelAndView modelAndView = new ModelAndView();
    given(userFacade.getTimezone()).willReturn("timezone X");


    //when
    new UserDataInterceptor(userFacade)
        .postHandle(null, null, null, modelAndView);


    //then
    assertThat(modelAndView).constains("timezone", "timezone X");
}
Single Responsibility Principle
SRP for tests

A test should have one and only one reason to fail.

P.S. This is definitely a good advice for unit tests, but rather not valid
for integration and end-to-end tests.
Testing two things at once
@DataProvider
public Object[][] data() {
         return new Object[][] { {"48", true}, {"+48", true},
         {"++48", true}, {"+48503", true}, {"+4", false},
         {"++4", false}, {"", false},
         {null, false}, {" ", false}, };
}

@Test(dataProvider = "data")
public void testQueryVerification(String query, boolean expected) {
         assertEquals(expected, FieldVerifier.isValidQuery(query));
}
Testing two things at once
@DataProvider
public Object[][] data() {
         return new Object[][] { {"48", true}, {"+48", true},
                                                                       Data
         {"++48", true}, {"+48503", true}, {"+4", false},
         {"++4", false}, {"", false},
         {null, false}, {" ", false}, };
}

@Test(dataProvider = "data")                                      Algorithm   / Logic
public void testQueryVerification(String query, boolean expected) {
         assertEquals(expected, FieldVerifier.isValidQuery(query));
}
Testing two things at once
@DataProvider
public Object[][] data() {
         return new Object[][] { {"48", true}, {"+48", true},
         {"++48", true}, {"+48503", true}, {"+4", false},
         {"++4", false}, {"", false},
         {null, false}, {" ", false}, };
}

@Test(dataProvider = "data")
public void testQueryVerification(String query, boolean expected) {
         assertEquals(expected, FieldVerifier.isValidQuery(query));
}

          testQueryVerification1() {
                   assertEquals(true,   FieldVerifier.isValidQuery(„48”));
          }
          testQueryVerification2() {
                   assertEquals(true,   FieldVerifier.isValidQuery(„+48”));
          }
          testQueryVerification3() {
                   assertEquals(true,   FieldVerifier.isValidQuery(„++48”));
          }
          testQueryVerification4() {
                   assertEquals(true,   FieldVerifier.isValidQuery(„+48503”));
          }
          ...
Testing two things at once
@DataProvider
public Object[][] data() {
       return new Object[][] { {"48", true}, {"+48", true},
       {"++48", true}, {"+48503", true}, {"+4", false},
       {"++4", false}, {"", false},
       {null, false}, {"     ", false}, };
}


@Test(dataProvider = "data")
public void testQueryVerification(String query, boolean expected) {
       assertEquals(expected, FieldVerifier.isValidQuery(query));
}
Concentrate on one feature
@DataProvider
public Object[][] validQueries() {
  return new Object[][] { {"48"}, {"48123"},
         {"+48"}, {"++48"}, {"+48503"}};
}

@Test(dataProvider = "validQueries")
public void shouldRecognizeValidQueries(
        String validQuery) {
  assertTrue(FieldVerifier.isValidQuery(validQuery));
}


@DataProvider
public Object[][] invalidQueries() {
  return new Object[][] {
         {"+4"}, {"++4"},
         {""}, {null}, {" "} };
}

@Test(dataProvider = "invalidQueries")
public void shouldRejectInvalidQueries(
        String invalidQuery) {
  assertFalse(FieldVerifier.isValidQuery(invalidQuery));
}
Are you satisfied?
Happy path

testSum() {
       assertEquals(Math.sum(2,2), 4);
}




                            http://mw2.google.com/mw-panoramio/photos/medium/68775332.jpg
Happy paths are for wimps

2 + 2
2 + -2
2 + -5
0 + 2
2 + 0
Integer.MAX_VALUE + something
etc.




                                http://kidskidskids.tumblr.com/post/1145294997
Avoiding happy paths

Start with one:
testSum() {
       assertEquals(Math.sum(2,2), 4);
}

Do the simplest thing that works:
sum(int x, int y) {
        return 4;
}


And then listen to your code.
Because it tells you something.          http://kidskidskids.tumblr.com/post/1145294997
Avoiding happy paths

 sum(int x, int y) {
       return 4;
 }


You moron!
Your test is so pathetic,
that I can make it pass
by doing such a silly thing.
Try harder!
                           http://looneytunes09.files.wordpress.com/2010/07/lisa-yell.gif
Readability is the king
Who the heck is “user_2” ?

@DataProvider
public static Object[][] usersPermissions() {
    return new Object[][]{
         {"user_1", Permission.READ},
         {"user_1", Permission.WRITE},
         {"user_1", Permission.REMOVE},
         {"user_2", Permission.WRITE},
         {"user_2", Permission.READ},
         {"user_3", Permission.READ}
    };
}
Ah, logged user can read and write...

@DataProvider
public static Object[][] usersPermissions() {
    return new Object[][]{
         {ADMIN, Permission.READ},
         {ADMIN, Permission.WRITE},
         {ADMIN, Permission.REMOVE},
         {LOGGED, Permission.WRITE},
         {LOGGED, Permission.READ},
         {GUEST, Permission.READ}
    };
}
domain1, domain2, domain3, ...
domain1, domain2, domain3, ...
domain1, domain2, domain3, ...
Do not make me learn the API!

  server = new MockServer(responseMap, true,
              new URL(SERVER_ROOT).getPort(), false);
Do not make me learn the API!

   server = new MockServer(responseMap, true,
               new URL(SERVER_ROOT).getPort(), false);




private static final boolean RESPONSE_IS_A_FILE = true;
private static final boolean NO_SSL = false;


server = new MockServer(responseMap, RESPONSE_IS_A_FILE,
                new URL(SERVER_ROOT).getPort(), NO_SSL);
Do not make me learn the API!

  server = new MockServer(responseMap, true,
              new URL(SERVER_ROOT).getPort(), false);




         server = new MockServerBuilder()
                .withResponse(responseMap)
                .withResponseType(FILE)
                .withUrl(SERVER_ROOT)
                .withoutSsl().create();
What is really important?
What is really important?
@DataProvider
public Object[][] snapshotArtifacts() {
       return new Object[][]{
            {"a", "b", "2.2-SNAPSHOT", Artifact.JAR },
            {"c", "d", "2.2.4.6-SNAPSHOT", Artifact.JAR},
            {"e", "f", "2-SNAPSHOT", Artifact.JAR}
       };
}
@Test(dataProvider = "snapshotArtifacts")
public void shouldRecognizeSnapshots(
       String groupId, String artifactId,
       String version, Type type) {
       Artifact artifact
            = new Artifact(groupId, artifactId, version, type);
       assertThat(artifact.isSnapshot()).isTrue();
}
Only version matters
@DataProvider
public Object[][] snapshotVersions() {
       return new Object[][]{
            {"2.2-SNAPSHOT"},
            {"2.2.4.6-SNAPSHOT"},
            {"2-SNAPSHOT"}
       };
}
@Test(dataProvider = "snapshotVersions")
public void shouldRecognizeSnapshots(String version) {
       Artifact artifact
            = new Artifact(VALID_GROUP, VALID_ARTIFACT_ID,
            version, VALID_TYPE);
       assertThat(artifact.isSnapshot()).isTrue();
}
Test method names
Naming is really important
Test methods names are important

• When test fails
• Relation to focused tests
Test methods names are important

• testFindTransactionsToAutoCharge()
• testSystemSuccess()
• testOperation()


      @Test
      public void testOperation() {
          configureRequest("/validate")
          rc = new RequestContext(parser, request)
          assert rc.getConnector() == null
          assert rc.getOperation().equals("validate")
      }
“should” is better than “test”


•   testOperation()
•   testQuery()
•   testConstructor()
•   testFindUsersWithFilter()


•   shouldRejectInvalidRequests()
•   shouldSaveNewUserToDatabase()
•   constructorShouldFailWithNegativePrice()
•   shouldReturnOnlyUsersWithGivenName()
“should” is better than “test”

• Starting test method names
  with “should” steers you in
  the right direction.
                                    http://jochopra.blogspot.com/




• “test” prefix makes your test
  method a limitless bag
  where you throw everything
  worth testing


                                  http://www.greenerideal.com/
Test methods names are important
@Test
public void testQuery(){
    when(q.getResultList()).thenReturn(null);
    assertNull(dao.findByQuery(Transaction.class, q, false));
    assertNull(dao.findByQuery(Operator.class, q, false));
    assertNull(dao.findByQuery(null, null, false));

    List result = new LinkedList();
    when(q.getResultList()).thenReturn(result);
    assertEquals(dao.findByQuery(Transaction.class, q, false), result);
    assertEquals(dao.findByQuery(Operator.class, q, false), result);
    assertEquals(dao.findByQuery(null, null, false), null);

    when(q.getSingleResult()).thenReturn(null);
    assertEquals(dao.findByQuery(Transaction.class, q, true).size(), 0);
    assertEquals(dao.findByQuery(Operator.class, q, true).size(), 0);
    assertEquals(dao.findByQuery(null, null, true), null);

    when(q.getSingleResult()).thenReturn(t);
    assertSame(dao.findByQuery(Transaction.class, q, true).get(0), t);
    when(q.getSingleResult()).thenReturn(o);
    assertSame(dao.findByQuery(Operator.class, q, true).get(0), o);
    when(q.getSingleResult()).thenReturn(null);
    assertSame(dao.findByQuery(null, null, true), null);
}
Assertions
Assertion part is freaking huge
public void shouldPreDeployApplication() {
          // given
          Artifact artifact = mock(Artifact.class);
          when(artifact.getFileName()).thenReturn("war-artifact-2.0.war");
          ServerConfiguration config
               = new ServerConfiguration(ADDRESS, USER, KEY_FILE, TOMCAT_PATH, TEMP_PATH);
          Tomcat tomcat = new Tomcat(HTTP_TOMCAT_URL, config);
          String destDir = new File(".").getCanonicalPath() + SLASH + "target" + SLASH;
          new File(destDir).mkdirs();

         // when
         tomcat.preDeploy(artifact, new FakeWar(WAR_FILE_LENGTH));

         //then
         JSch jsch = new JSch();
         jsch.addIdentity(KEY_FILE);
         Session session = jsch.getSession(USER, ADDRESS, 22);
         session.setConfig("StrictHostKeyChecking", "no");
         session.connect();

         Channel channel = session.openChannel("sftp");
         session.setServerAliveInterval(92000);
         channel.connect();
         ChannelSftp sftpChannel = (ChannelSftp) channel;

         sftpChannel.get(TEMP_PATH + SLASH + artifact.getFileName(), destDir);
         sftpChannel.exit();

         session.disconnect();

         File downloadedFile = new File(destDir, artifact.getFileName());

         assertThat(downloadedFile).exists().hasSize(WAR_FILE_LENGTH);
}
Just say it

public void shouldPreDeployApplication() {
      Artifact artifact = mock(Artifact.class);
      when(artifact.getFileName())
         .thenReturn(ARTIFACT_FILE_NAME);
      ServerConfiguration config
         = new ServerConfiguration(ADDRESS, USER,
             KEY_FILE, TOMCAT_PATH, TEMP_PATH);
      Tomcat tomcat = new Tomcat(HTTP_TOMCAT_URL, config);


      tomcat.preDeploy(artifact, new FakeWar(WAR_FILE_LENGTH));


      SSHServerAssert.assertThat(ARTIFACT_FILE_NAME)
         .existsOnServer(config).hasSize(WAR_FILE_LENGTH);
}
Asserting using private methods

@Test
public void testChargeInRetryingState() throws Exception {
    // given
        TxDTO request = createTxDTO(RequestType.CHARGE);
        AndroidTransaction androidTransaction = ...


    // when
    final TxDTO txDTO = processor.processRequest(request);


    // then
    assertState(request, androidTransaction,
        CHARGED, CHARGE_PENDING, AS_ANDROID_TX_STATE,
        ClientMessage.SUCCESS, ResultCode.SUCCESS);
}
Matchers vs. private methods
assertState(TxDTO txDTO, AndroidTransaction androidTransaction,
         AndroidTransactionState expectedAndroidState,
       AndroidTransactionState expectedPreviousAndroidState,
       ExtendedState expectedState,
       String expectedClientStatus,
         ResultCode expectedRequestResultCode) {
    final List<AndroidTransactionStep> steps
         = new ArrayList<>(androidTransaction.getTransactionSteps());
    final boolean checkPreviousStep = expectedAndroidState != null;
    assertTrue(steps.size() >= (checkPreviousStep ? 3 : 2));

     if (checkPreviousStep) {
         AndroidTransactionStep lastStep = steps.get(steps.size() - 2);
         assertEquals(lastStep.getTransactionState(),
              expectedPreviousAndroidState);
     }

     final AndroidTransactionStep lastStep = steps.get(steps.size() - 1);
     assertEquals(lastStep.getTransactionState(), expectedAndroidState);
     assertEquals(lastStep.getMessage(), expectedClientStatus);

     assertEquals(txDTO.getResultCode(), expectedRequestResultCode);
     assertEquals(androidTransaction.getState(), expectedAndroidState);
     assertEquals(androidTransaction.getExtendedState(), expectedState);

     if (expectedClientStatus == null) {
         verifyZeroInteractions(client);
     }
}
Matchers vs. private methods

@Test
public void testChargeInRetryingState() throws Exception {
    // given
    TxDTO request = createTxDTO(CHARGE);
    AndroidTransaction androidTransaction = ...
    // when
    final TxDTO txDTO = processor.processRequest(request);
    // then
    assertThat(androidTransaction).hasState(CHARGED)
          .hasMessage(ClientMessage.SUCCESS)
          .hasPreviousState(CHARGE_PENDING)
          .hasExtendedState(null);
    assertEquals(txDTO.getResultCode(),
          ResultCode.SUCCESS);
}
What is asserted?

@Test
public void testCompile_32Bit_FakeSourceFile() {
        CompilerSupport _32BitCompilerSupport
           = CompilerSupportFactory.getDefault32BitCompilerSupport();
        testCompile_FakeSourceFile(_32BitCompilerSupport);
}
What is asserted?

@Test
public void testCompile_32Bit_FakeSourceFile() {
        CompilerSupport _32BitCompilerSupport
           = CompilerSupportFactory.getDefault32BitCompilerSupport();
        testCompile_FakeSourceFile(_32BitCompilerSupport);
}


private void testCompile_FakeSourceFile(
               CompilerSupport compilerSupport) {
        compiledFiles
           = compilerSupport.compile(new File[] { new File("fake") });
        assertThat(compiledFiles, is(emptyArray()));
}
Asserting everything

public void invalidTxShouldBeCanceled() {
    String fileContent =
       FileUtils.getContentOfFile("response.csv");
    assertTrue(fileContent.contains(
       "CANCEL,123,123cancel,billing_id_123_cancel,SUCCESS,"));
}
Asserting everything

public void invalidTxShouldBeCanceled() {
    String fileContent =
       FileUtils.getContentOfFile("response.csv");
    assertTrue(fileContent.contains(
       "CANCEL,123,123cancel,billing_id_123_cancel,SUCCESS,"));
}



public void invalidTxShouldBeCanceled() {
    String fileContent =
        FileUtils.getContentOfFile("response.csv");
    TxDTOAssert.assertThat(fileContent)
          .hasTransaction("123cancel").withResultCode(SUCCESS);
}
Know your tool
Asynchronous Calls

@Test
public void updatesCustomerStatus() throws Exception {
    // Publish an asynchronous event:
    publishEvent(updateCustomerStatusEvent);
    // Awaitility lets you wait
        // until the asynchronous operation completes:
    await()
           .atMost(5, SECONDS)
           .until(costumerStatusIsUpdated());
    ...
}                                http://code.google.com/p/awaitility/
Expected exceptions

@Test(expectedExceptions = SmsException.class)
public void shouldThrowException() throws SmsException {
    try {
        String s = gutExtractor.extractGut(„invalid gut”);
        System.out.println(s);
    } catch (SmsException e) {
        e.printStackTrace();
        throw e;
    }
}
Expected exceptions

@Test(expectedExceptions = SmsException.class)
public void shouldThrowException() throws SmsException {
    String s = gutExtractor.extractGut(„invalid gut”);
}
Expected exceptions (with catch-exception)

@Test
public void shouldThrowException() throws SmsException {


        when(gutExtractor.extractGut(„invalid gut”));


        then(caughtException())
         .isInstanceOf(SmsException.class)
         .hasMessage("Invalid gut")
         .hasNoCause();
}


                                  http://code.google.com/p/catch-exception/
Running SUT's code concurrently

@Test(threadPoolSize = 3, invocationCount = 10)
public void testServer() {
    // this method will be run in parallel by 3 thread
    // 10 invocations (in total)
}
Dependent test methods

@Test
public void shouldConnectToDB() {
    // verifying that you can
    // estabilish a connection with DB
}




@Test(dependsOnMethods = „shouldConnectToDB”)
public void should…() {
    // some operations on DB
}
Know your tools
•   Unit testing framework           Additional libraries
      Use of temporary file rule
                                          Hamcrest, FEST, Mockito,
      Listeners                           catch-exception, awaitility, …
      Concurrency                  •   Build tool
      @Before/@After                     Parallel execution

      Parametrized tests            CI

      Test dependencies            •   IDE
                                           Templates
                                          Shortcuts
What do you really want to test?
What do you really want to test?

  @Test
  public void shouldAddAUser() {
      User user = new User();
      userService.save(user);
      assertEquals(dao.getNbOfUsers(), 1);
  }
You wanted to see that the number increased

 @Test
 public void shouldAddAUser() {
         int nb = dao.getNbOfUsers();
         User user = new User();
         userService.save(user);
         assertEquals(dao.getNbOfUsers(), nb + 1);
 }
Random
Doing it wrong
public void myTest() {
     SomeObject obj = new SomeObject(
        a, b, c, productCode());
     // testing of obj here
}

private String productCode(){
     String[] codes =
        {"Code A", "Code B",
        "Code C", "Code D"};
     int index = rand.nextInt(codes.length);
   return codes[index];
}
The dream of stronger, random-powered tests
public void myTest() {
     SomeObject obj = new SomeObject(
        randomName(), randomValue(), ....);
     // testing of obj here
}

Does it make your test stronger?
The dream of stronger, random-powered tests
public void myTest() {
     SomeObject obj = new SomeObject(
        randomName(), randomValue(), ....);
     // testing of obj here
}

Does it make your test stronger?
...or does it only bring confusion?
Test failed
Expected
     SomeObject(„a”, „b”, ....)
but got
     SomeObject(„*&O*$NdlF”, „#idSLNF”, ....)
Conclusions
There is more to it

• Integration / end-to-end tests which are not parametrized
  (so they all try to set up jetty on port 8080),
• Tests which should be really unit, but use Spring context
  to create objects,
• Tests with a lot of dependencies between them (a
  nightmare to maintain!),
• Tests which run slow
• Tests which try to cover the deficiencies of production
  code and end up being a total mess,
• Tests which verify methods instead of verifying
  responsibilities of a class
• etc., etc.
Test-last? No!


• makes people not write tests at all
• makes people do only happy-testing
• tests reflect the implementation
Always TDD?

For six or eight hours spread over the next few weeks I
struggled to get the first test written and running. Writing
tests for Eclipse plug-ins is not trivial, so it’s not
surprising I had some trouble. [...] In six or eight hours
of solid programming time, I can still make significant
progress. If I’d just written some stuff and verified it by
hand, I would probably have the final answer to whether
my idea is actually worth money by now. Instead, all I
have is a complicated test that doesn’t work, a pile
of frustration, eight fewer hours in my life, and the
motivation to write another essay.
                                           Kent Beck, Just Ship, Baby
Treat tests as the first class citizens
•    do it everyday or forget about it          •   make tests readable using matchers,
•    use the right tool for the job                 builders and good names
      • and learn to use it!                    •   test behaviour not methods
•    do not live with broken windows            •   be pragmatic about the tests you write
•    respect KISS, SRP, DRY (?)                      • TDD always?
•    write good code, and you will also write       •   what is the best way to test it?
     good tests                                         unit/integration/end-to-end ?
      • or rather write good tests and you      •   automate!
          will get good code for free           •   always concentrate on what is worth
•    code review your tests                         testing
•    do more than happy path testing                 • ask yourself questions like: 'is it
                                                         really important that X should send
•    do not make the reader learn the API,
                                                         message Y to Z?'
     make it obvious
                                                •   use the front door – state testing before
•    bad names lead to bad tests
                                                    interaction testing (mockc)
…questions?
…rants?
…hate speeches?
…any other forms of expressing your ego?
Thank you!
Thank you for watching these
slides! You can learn more about
wirting high quality tests by
reading my book – „Practical Unit
Testing with TestNG and
Mockito”.

You can also participate in
writing of my
new (free!) e-book devoted to
bad and good tests.

Contenu connexe

Tendances

An introduction to Google test framework
An introduction to Google test frameworkAn introduction to Google test framework
An introduction to Google test frameworkAbner Chih Yi Huang
 
Understanding JavaScript Testing
Understanding JavaScript TestingUnderstanding JavaScript Testing
Understanding JavaScript Testingjeresig
 
JUnit- A Unit Testing Framework
JUnit- A Unit Testing FrameworkJUnit- A Unit Testing Framework
JUnit- A Unit Testing FrameworkOnkar Deshpande
 
Understanding JavaScript Testing
Understanding JavaScript TestingUnderstanding JavaScript Testing
Understanding JavaScript TestingKissy Team
 
xUnit Style Database Testing
xUnit Style Database TestingxUnit Style Database Testing
xUnit Style Database TestingChris Oldwood
 
Mocking in Java with Mockito
Mocking in Java with MockitoMocking in Java with Mockito
Mocking in Java with MockitoRichard Paul
 
Advanced junit and mockito
Advanced junit and mockitoAdvanced junit and mockito
Advanced junit and mockitoMathieu Carbou
 
C++ Unit Test with Google Testing Framework
C++ Unit Test with Google Testing FrameworkC++ Unit Test with Google Testing Framework
C++ Unit Test with Google Testing FrameworkHumberto Marchezi
 
Test driven development - JUnit basics and best practices
Test driven development - JUnit basics and best practicesTest driven development - JUnit basics and best practices
Test driven development - JUnit basics and best practicesNarendra Pathai
 
Unit testing best practices with JUnit
Unit testing best practices with JUnitUnit testing best practices with JUnit
Unit testing best practices with JUnitinTwentyEight Minutes
 
Unit testing with PHPUnit - there's life outside of TDD
Unit testing with PHPUnit - there's life outside of TDDUnit testing with PHPUnit - there's life outside of TDD
Unit testing with PHPUnit - there's life outside of TDDPaweł Michalik
 
Unit testing with Junit
Unit testing with JunitUnit testing with Junit
Unit testing with JunitValerio Maggio
 
Introduction to Unit Testing with PHPUnit
Introduction to Unit Testing with PHPUnitIntroduction to Unit Testing with PHPUnit
Introduction to Unit Testing with PHPUnitMichelangelo van Dam
 

Tendances (20)

Junit
JunitJunit
Junit
 
Junit
JunitJunit
Junit
 
Testing with Junit4
Testing with Junit4Testing with Junit4
Testing with Junit4
 
Unit testing with java
Unit testing with javaUnit testing with java
Unit testing with java
 
An introduction to Google test framework
An introduction to Google test frameworkAn introduction to Google test framework
An introduction to Google test framework
 
Understanding JavaScript Testing
Understanding JavaScript TestingUnderstanding JavaScript Testing
Understanding JavaScript Testing
 
JUnit- A Unit Testing Framework
JUnit- A Unit Testing FrameworkJUnit- A Unit Testing Framework
JUnit- A Unit Testing Framework
 
Understanding JavaScript Testing
Understanding JavaScript TestingUnderstanding JavaScript Testing
Understanding JavaScript Testing
 
xUnit Style Database Testing
xUnit Style Database TestingxUnit Style Database Testing
xUnit Style Database Testing
 
Mocking in Java with Mockito
Mocking in Java with MockitoMocking in Java with Mockito
Mocking in Java with Mockito
 
Advanced junit and mockito
Advanced junit and mockitoAdvanced junit and mockito
Advanced junit and mockito
 
C++ Unit Test with Google Testing Framework
C++ Unit Test with Google Testing FrameworkC++ Unit Test with Google Testing Framework
C++ Unit Test with Google Testing Framework
 
Junit and testNG
Junit and testNGJunit and testNG
Junit and testNG
 
Test driven development - JUnit basics and best practices
Test driven development - JUnit basics and best practicesTest driven development - JUnit basics and best practices
Test driven development - JUnit basics and best practices
 
JUNit Presentation
JUNit PresentationJUNit Presentation
JUNit Presentation
 
Unit testing with JUnit
Unit testing with JUnitUnit testing with JUnit
Unit testing with JUnit
 
Unit testing best practices with JUnit
Unit testing best practices with JUnitUnit testing best practices with JUnit
Unit testing best practices with JUnit
 
Unit testing with PHPUnit - there's life outside of TDD
Unit testing with PHPUnit - there's life outside of TDDUnit testing with PHPUnit - there's life outside of TDD
Unit testing with PHPUnit - there's life outside of TDD
 
Unit testing with Junit
Unit testing with JunitUnit testing with Junit
Unit testing with Junit
 
Introduction to Unit Testing with PHPUnit
Introduction to Unit Testing with PHPUnitIntroduction to Unit Testing with PHPUnit
Introduction to Unit Testing with PHPUnit
 

Similaire à Bad Tests, Good Tests Guide

GeeCON 2012 Bad Tests, Good Tests
GeeCON 2012 Bad Tests, Good TestsGeeCON 2012 Bad Tests, Good Tests
GeeCON 2012 Bad Tests, Good TestsTomek Kaczanowski
 
2012 JDays Bad Tests Good Tests
2012 JDays Bad Tests Good Tests2012 JDays Bad Tests Good Tests
2012 JDays Bad Tests Good TestsTomek Kaczanowski
 
J unit presentation
J unit presentationJ unit presentation
J unit presentationPriya Sharma
 
How to Start Test-Driven Development in Legacy Code
How to Start Test-Driven Development in Legacy CodeHow to Start Test-Driven Development in Legacy Code
How to Start Test-Driven Development in Legacy CodeDaniel Wellman
 
How to write clean tests
How to write clean testsHow to write clean tests
How to write clean testsDanylenko Max
 
Testing, Performance Analysis, and jQuery 1.4
Testing, Performance Analysis, and jQuery 1.4Testing, Performance Analysis, and jQuery 1.4
Testing, Performance Analysis, and jQuery 1.4jeresig
 
Junit4&testng presentation
Junit4&testng presentationJunit4&testng presentation
Junit4&testng presentationSanjib Dhar
 
Pragmatic unittestingwithj unit
Pragmatic unittestingwithj unitPragmatic unittestingwithj unit
Pragmatic unittestingwithj unitliminescence
 
Description (Part A) In this lab you will write a Queue implementati.pdf
Description (Part A) In this lab you will write a Queue implementati.pdfDescription (Part A) In this lab you will write a Queue implementati.pdf
Description (Part A) In this lab you will write a Queue implementati.pdfrishabjain5053
 
Developer Test - Things to Know
Developer Test - Things to KnowDeveloper Test - Things to Know
Developer Test - Things to Knowvilniusjug
 
PQTimer.java A simple driver program to run timing t.docx
  PQTimer.java     A simple driver program to run timing t.docx  PQTimer.java     A simple driver program to run timing t.docx
PQTimer.java A simple driver program to run timing t.docxjoyjonna282
 
Unit testing in iOS featuring OCUnit, GHUnit & OCMock
Unit testing in iOS featuring OCUnit, GHUnit & OCMockUnit testing in iOS featuring OCUnit, GHUnit & OCMock
Unit testing in iOS featuring OCUnit, GHUnit & OCMockRobot Media
 

Similaire à Bad Tests, Good Tests Guide (20)

GeeCON 2012 Bad Tests, Good Tests
GeeCON 2012 Bad Tests, Good TestsGeeCON 2012 Bad Tests, Good Tests
GeeCON 2012 Bad Tests, Good Tests
 
2012 JDays Bad Tests Good Tests
2012 JDays Bad Tests Good Tests2012 JDays Bad Tests Good Tests
2012 JDays Bad Tests Good Tests
 
J unit presentation
J unit presentationJ unit presentation
J unit presentation
 
JUnit Presentation
JUnit PresentationJUnit Presentation
JUnit Presentation
 
How to Start Test-Driven Development in Legacy Code
How to Start Test-Driven Development in Legacy CodeHow to Start Test-Driven Development in Legacy Code
How to Start Test-Driven Development in Legacy Code
 
How to write clean tests
How to write clean testsHow to write clean tests
How to write clean tests
 
Testing, Performance Analysis, and jQuery 1.4
Testing, Performance Analysis, and jQuery 1.4Testing, Performance Analysis, and jQuery 1.4
Testing, Performance Analysis, and jQuery 1.4
 
J Unit
J UnitJ Unit
J Unit
 
Junit4&testng presentation
Junit4&testng presentationJunit4&testng presentation
Junit4&testng presentation
 
Junit With Eclipse
Junit With EclipseJunit With Eclipse
Junit With Eclipse
 
Pragmatic unittestingwithj unit
Pragmatic unittestingwithj unitPragmatic unittestingwithj unit
Pragmatic unittestingwithj unit
 
Presentation Unit Testing process
Presentation Unit Testing processPresentation Unit Testing process
Presentation Unit Testing process
 
Description (Part A) In this lab you will write a Queue implementati.pdf
Description (Part A) In this lab you will write a Queue implementati.pdfDescription (Part A) In this lab you will write a Queue implementati.pdf
Description (Part A) In this lab you will write a Queue implementati.pdf
 
Developer Test - Things to Know
Developer Test - Things to KnowDeveloper Test - Things to Know
Developer Test - Things to Know
 
PQTimer.java A simple driver program to run timing t.docx
  PQTimer.java     A simple driver program to run timing t.docx  PQTimer.java     A simple driver program to run timing t.docx
PQTimer.java A simple driver program to run timing t.docx
 
Google guava
Google guavaGoogle guava
Google guava
 
Java programs
Java programsJava programs
Java programs
 
Unit testing in iOS featuring OCUnit, GHUnit & OCMock
Unit testing in iOS featuring OCUnit, GHUnit & OCMockUnit testing in iOS featuring OCUnit, GHUnit & OCMock
Unit testing in iOS featuring OCUnit, GHUnit & OCMock
 
Good Tests Bad Tests
Good Tests Bad TestsGood Tests Bad Tests
Good Tests Bad Tests
 
TestNG vs Junit
TestNG vs JunitTestNG vs Junit
TestNG vs Junit
 

Plus de Tomek Kaczanowski

Grupowe podejmowanie decyzji
Grupowe podejmowanie decyzjiGrupowe podejmowanie decyzji
Grupowe podejmowanie decyzjiTomek Kaczanowski
 
Practical Unit Testing with TestNG and Mockito
Practical Unit Testing with TestNG and MockitoPractical Unit Testing with TestNG and Mockito
Practical Unit Testing with TestNG and MockitoTomek Kaczanowski
 
GeeCON 2011 Who Watches The Watchmen? - On Quality Of Tests
GeeCON 2011 Who Watches The Watchmen? - On Quality Of TestsGeeCON 2011 Who Watches The Watchmen? - On Quality Of Tests
GeeCON 2011 Who Watches The Watchmen? - On Quality Of TestsTomek Kaczanowski
 
Convention Over Configuration - Maven 3, Polyglot Maven, Gradle and Ant
Convention Over Configuration - Maven 3, Polyglot Maven, Gradle and AntConvention Over Configuration - Maven 3, Polyglot Maven, Gradle and Ant
Convention Over Configuration - Maven 3, Polyglot Maven, Gradle and AntTomek Kaczanowski
 
Gradle talk, Javarsovia 2010
Gradle talk, Javarsovia 2010Gradle talk, Javarsovia 2010
Gradle talk, Javarsovia 2010Tomek Kaczanowski
 

Plus de Tomek Kaczanowski (6)

2015 ACE! Conference slides
2015 ACE! Conference slides2015 ACE! Conference slides
2015 ACE! Conference slides
 
Grupowe podejmowanie decyzji
Grupowe podejmowanie decyzjiGrupowe podejmowanie decyzji
Grupowe podejmowanie decyzji
 
Practical Unit Testing with TestNG and Mockito
Practical Unit Testing with TestNG and MockitoPractical Unit Testing with TestNG and Mockito
Practical Unit Testing with TestNG and Mockito
 
GeeCON 2011 Who Watches The Watchmen? - On Quality Of Tests
GeeCON 2011 Who Watches The Watchmen? - On Quality Of TestsGeeCON 2011 Who Watches The Watchmen? - On Quality Of Tests
GeeCON 2011 Who Watches The Watchmen? - On Quality Of Tests
 
Convention Over Configuration - Maven 3, Polyglot Maven, Gradle and Ant
Convention Over Configuration - Maven 3, Polyglot Maven, Gradle and AntConvention Over Configuration - Maven 3, Polyglot Maven, Gradle and Ant
Convention Over Configuration - Maven 3, Polyglot Maven, Gradle and Ant
 
Gradle talk, Javarsovia 2010
Gradle talk, Javarsovia 2010Gradle talk, Javarsovia 2010
Gradle talk, Javarsovia 2010
 

Dernier

DevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache MavenDevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache MavenHervé Boutemy
 
Gen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfGen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfAddepto
 
Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024Scott Keck-Warren
 
Search Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfSearch Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfRankYa
 
My INSURER PTE LTD - Insurtech Innovation Award 2024
My INSURER PTE LTD - Insurtech Innovation Award 2024My INSURER PTE LTD - Insurtech Innovation Award 2024
My INSURER PTE LTD - Insurtech Innovation Award 2024The Digital Insurer
 
Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Commit University
 
Install Stable Diffusion in windows machine
Install Stable Diffusion in windows machineInstall Stable Diffusion in windows machine
Install Stable Diffusion in windows machinePadma Pradeep
 
SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024Lorenzo Miniero
 
Powerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time ClashPowerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time Clashcharlottematthew16
 
Human Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsHuman Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsMark Billinghurst
 
Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 3652toLead Limited
 
CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):comworks
 
Commit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyCommit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyAlfredo García Lavilla
 
Vertex AI Gemini Prompt Engineering Tips
Vertex AI Gemini Prompt Engineering TipsVertex AI Gemini Prompt Engineering Tips
Vertex AI Gemini Prompt Engineering TipsMiki Katsuragi
 
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024BookNet Canada
 
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024BookNet Canada
 
AI as an Interface for Commercial Buildings
AI as an Interface for Commercial BuildingsAI as an Interface for Commercial Buildings
AI as an Interface for Commercial BuildingsMemoori
 
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Mark Simos
 
Unraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdfUnraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdfAlex Barbosa Coqueiro
 

Dernier (20)

E-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptx
E-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptxE-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptx
E-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptx
 
DevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache MavenDevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache Maven
 
Gen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfGen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdf
 
Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024
 
Search Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfSearch Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdf
 
My INSURER PTE LTD - Insurtech Innovation Award 2024
My INSURER PTE LTD - Insurtech Innovation Award 2024My INSURER PTE LTD - Insurtech Innovation Award 2024
My INSURER PTE LTD - Insurtech Innovation Award 2024
 
Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!
 
Install Stable Diffusion in windows machine
Install Stable Diffusion in windows machineInstall Stable Diffusion in windows machine
Install Stable Diffusion in windows machine
 
SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024
 
Powerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time ClashPowerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time Clash
 
Human Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsHuman Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR Systems
 
Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365
 
CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):
 
Commit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyCommit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easy
 
Vertex AI Gemini Prompt Engineering Tips
Vertex AI Gemini Prompt Engineering TipsVertex AI Gemini Prompt Engineering Tips
Vertex AI Gemini Prompt Engineering Tips
 
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
 
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
 
AI as an Interface for Commercial Buildings
AI as an Interface for Commercial BuildingsAI as an Interface for Commercial Buildings
AI as an Interface for Commercial Buildings
 
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
 
Unraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdfUnraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdf
 

Bad Tests, Good Tests Guide

  • 1. Bad Tests, Good Tests Tomek Kaczanowski
  • 3. Tomek Kaczanowski • Developer • Team lead • Blogger • http://kaczanowscy.pl/tomek • Book author • http://practicalunittesting.com • Working at CodeWise (Krakow)  ...we are hiring, wanna join us?
  • 4. Before we begin • Most of the examples are real but:  Obfuscated − to protect the innocents  Truncated − imagine much more complex domain objects • Asking questions is allowed  ...but being smarter than me is not ;)
  • 5. A little bit of history
  • 6.
  • 7. Please... no more... http://thedogatemycareplan.wordpress.com
  • 8. Before we begin • The tests were written in 2004-2006. • No automation, no CI. • Some tests do not compile. • In some tests you can read a comment that "WARNING: test requires the divide param to be set to 20" but the code is so ugly, that there is no way to inject this value. • Some test data are available in form of serialized objects (*.ser) that can not be currently deserialized, because the classes have changed. • The project is now in maintenance. Courtesy of Bartosz http://twitter.com/#!/bocytko
  • 9. We don't need no stinkin' asserts! public void testAddChunks() { System.out.println("*************************************"); System.out.println("testAddChunks() ... "); ChunkMap cm = new ChunkMap(3); cm.addChunk(new Chunk("chunk")); List testList = cm.getChunks("chunk",null); if (testList.isEmpty()) fail("there should be at least one list!"); Chunk chunk = cm.getActualChunk("chunk",null); if (chunk.getElements().isEmpty()) fail("there should be at least one element!"); if (cm.getFinalChunkNr() != 1) fail("there should be at least one chunk!"); // iterate actual chunk for (Iterator it = chunk.getElements().iterator(); it.hasNext();) { Element element = (Element) it.next(); System.out.println("Element: " + element); } showChunks(cm); System.out.println("testAddChunks() OK "); }
  • 10. Success is not an option... /** * Method testFailure. */ public void testFailure() { try { Message message = new Message(null,true); fail(); } catch(Exception ex) { ExceptionHandler.log(ExceptionLevel.ANY,ex); fail(); } }
  • 11. What has happened? Well, it failed... public void testSimple() { IData data = null; IFormat format = null; LinkedList<String> attr = new LinkedList<String>(); attr.add("A"); attr.add("B"); try { format = new SimpleFormat("A"); data.setAmount(Amount.TEN); data.setAttributes(attr); IResult result = format.execute(); System.out.println(result.size()); Iterator iter = result.iterator(); while (iter.hasNext()) { IResult r = (IResult) iter.next(); System.out.println(r.getMessage()); ... } catch (Exception e) { fail(); } }
  • 12. What has happened? Well, it failed... public void testSimple() { IData data = null; IFormat format = null; LinkedList<String> attr = new LinkedList<String>(); attr.add("A"); attr.add("B"); data is still null here. Ready or not, NPE is coming. try { format = new SimpleFormat("A"); data.setAmount(Amount.TEN); data.setAttributes(attr); IResult result = format.execute(); System.out.println(result.size()); Iterator iter = result.iterator(); while (iter.hasNext()) { IResult r = (IResult) iter.next(); System.out.println(r.getMessage()); ... } catch (Exception e) { fail(); } }
  • 13. Talk to me //wait for messages do { input = ""; try { System.out.print(">"); read = System.in.read(buf); //convert characters to string input = new String(buf, 0, read - newline.length()); System.out.println(input); if (input.equals("end") || input.equals("exit") || input.equals("stop") || input.equals("quit")) { System.out.println("Terminating Test please wait..."); System.out.println("******* Test terminated *******"); toStop = true; } else { System.out.println("Commands:" + newline + "'end', 'exit', 'stop' or 'quit' terminates this test "); } } catch (Exception e) { e.printStackTrace(); } } while (!toStop);
  • 14. Tests are boring – let us autogenerate them! /** protected void tearDown() throws Exception { * Generated by JUnitDoclet, a tool provided by // JUnitDoclet begin method testcase.tearDown * ObjectFab GmbH under LGPL. adapter = null; * Please see www.junitdoclet.org, www.gnu.org super.tearDown(); * and www.objectfab.de for informations about // JUnitDoclet end method testcase.tearDown * the tool, the licence and the authors. */ public void testMain() throws Exception { public class AdapterTest // JUnitDoclet begin method testMain // JUnitDoclet begin extends_implements Adapter.main(new String [] {"ADAPTER"}); extends TestCase // JUnitDoclet end method testMain // JUnitDoclet end extends_implements } { // JUnitDoclet begin class Adapter adapter = null; /** // JUnitDoclet end class * JUnitDoclet moves marker to this method, if there is not match * for them in the regenerated code and if the marker is not empty. public AdapterTest(String name) { * This way, no test gets lost when regenerating after renaming. // JUnitDoclet begin method AdapterTest * Method testVault is supposed to be empty. super(name); */ // JUnitDoclet end method AdapterTest public void testVault() throws Exception { } // JUnitDoclet begin method testcase.testVault // JUnitDoclet end method testcase.testVault public Adapter createInstance() throws Exception { } // JUnitDoclet begin method testcase.createInstance return new Adapter(); public static void main(String[] args) { // JUnitDoclet end method testcase.createInstance // JUnitDoclet begin method testcase.main } junit.textui.TestRunner.run(AdapterTest.class); // JUnitDoclet end method testcase.main protected void setUp() throws Exception { } // JUnitDoclet begin method testcase.setUp } super.setUp(); adapter = createInstance(); // JUnitDoclet end method testcase.setUp }
  • 15. Tests are boring – let us autogenerate them! public void testSetGetTimestamp() throws Exception { // JUnitDoclet begin method setTimestamp getTimestamp java.util.Calendar[] tests = {new GregorianCalendar(), null}; for (int i = 0; i < tests.length; i++) { adapter.setTimestamp(tests[i]); assertEquals(tests[i], adapter.getTimestamp()); } // JUnitDoclet end method setTimestamp getTimestamp } public void testSetGetParam() throws Exception { // JUnitDoclet begin method setParam getParam String[] tests = {"a", "aaa", "---", "23121313", "", null}; for (int i = 0; i < tests.length; i++) { adapter.setParam(tests[i]); assertEquals(tests[i], adapter.getParam()); } // JUnitDoclet end method setParam getParam }
  • 16. Bartosz, do you really work with such code?!
  • 17. Conclusions • Automation! • Running • Verification • Tests are to be written not generated • You should be informed why your test failed • Master your tools  …at least learn the basics!
  • 19. Why bother with tests? • System works as expected • Changes do not hurt • Documentation http://twitter.com/#!/devops_borat
  • 20. Tests help to achieve quality Not sure when I saw this picture – probably in GOOS?
  • 21. What happens if we do it wrong? • Angry clients • Depressed developers http://www.joshcanhelp.com
  • 22. When I started out with unit tests, I was enthralled with the promise of ease and security that they would bring to my projects. In practice, however, the theory of sustainable software through unit tests started to break down. This difficulty continued to build up, until I finally threw my head back in anger and declared that "Unit Tests have become more trouble than they are worth." Llewellyn Falco and Michael Kennedy, Develop Mentor August 09
  • 24. It is a full-time job
  • 25. No smoke without tests class SystemAdminSmokeTest extends GroovyTestCase { void testSmoke() { def ds = new org.h2.jdbcx.JdbcDataSource( URL: 'jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;MODE=Oracle', user: 'sa', password: '') def jpaProperties = new Properties() jpaProperties.setProperty( 'hibernate.cache.use_second_level_cache', 'false') jpaProperties.setProperty( 'hibernate.cache.use_query_cache', 'false') def emf = new LocalContainerEntityManagerFactoryBean( dataSource: ds, persistenceUnitName: 'my-domain', jpaVendorAdapter: new HibernateJpaVendorAdapter( database: Database.H2, showSql: true, generateDdl: true), jpaProperties: jpaProperties) }
  • 26. No smoke without tests class SystemAdminSmokeTest extends GroovyTestCase { void testSmoke() { // do not remove below code // def ds = new org.h2.jdbcx.JdbcDataSource( // URL: 'jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;MODE=Oracle', // user: 'sa', password: '') // // def jpaProperties = new Properties() // jpaProperties.setProperty( // 'hibernate.cache.use_second_level_cache', 'false') // jpaProperties.setProperty( // 'hibernate.cache.use_query_cache', 'false') // // def emf = new LocalContainerEntityManagerFactoryBean( // dataSource: ds, persistenceUnitName: 'my-domain', // jpaVendorAdapter: new HibernateJpaVendorAdapter( // database: Database.H2, showSql: true, // generateDdl: true), jpaProperties: jpaProperties) …some more code below, all commented out :( }
  • 27. Let's follow the leader! @Test public class ExampleTest { public void testExample() { assertTrue(true); } }
  • 28. Uh-oh, I feel lonely... @Test public class ExampleTest { public void testExample() { assertTrue(true); } }
  • 30. Asking for troubles... LoggingPropertyConfigurator configurator = mock(LoggingPropertyConfigurator.class); BaseServletContextListener baseServletContextListener = new BaseServletContextListener(configurator); Should load some @Test public void shouldLoadConfigProperties() { default config baseServletContextListener.contextInitialized(); verify(configurator).configure(any(Properties.class)); } @Test(expected = LoggingInitialisationException.class) Should load this public void shouldThrowLoggingException() { specific file System.setProperty("logConfig", "nonExistingFile"); baseServletContextListener.contextInitialized(); }
  • 32. Mock'em All! public String getUrl(User user, String timestamp) { String name=user.getFullName(); String url=baseUrl +"name="+URLEncoder.encode(name, "UTF-8") +"&timestamp="+timestamp; return url; } public String getUrl(User user) { Date date=new Date(); Long time=(date.getTime()/1000); //convert ms to seconds String timestamp=time.toString(); return getUrl(user, timestamp); }
  • 33. Mock'em All! public String getUrl(User user, String timestamp) { String name=user.getFullName(); String url=baseUrl +"name="+URLEncoder.encode(name, "UTF-8") +"&timestamp="+timestamp; return url; } public String getUrl(User user) { Date date=new Date(); Long time=(date.getTime()/1000); //convert ms to seconds String timestamp=time.toString(); return getUrl(user, timestamp); } @Test public void shouldUseTimestampMethod() { //given Util spyUtil = spy(util); //when spyUtil.getUrl(user); //then verify(spyUtil).getUrl(eq(user), anyString()); }
  • 34. Use the front door @Test public void shouldAddTimestampToGeneratedUrl() { //given util = new .... TimeProvider timeProvider = mock(TimeProvider.class); when(timeProvider.getTime()).thenReturn("12345"); util.set(timeProvider); //when String url = util.getUrl(user); //then assertThat(url).contains("timestamp=12345"); }
  • 35. Mock'em All! @Test public void shouldAddTimeZoneToModelAndView() { //given UserFacade userFacade = mock(UserFacade.class); ModelAndView modelAndView = mock(ModelAndView.class); given(userFacade.getTimezone()).willReturn("timezone X"); //when new UserDataInterceptor(userFacade) .postHandle(null, null, null, modelAndView); //then verify(modelAndView).addObject("timezone", "timezone X"); }
  • 36. Use the front door @Test public void shouldAddTimeZoneToModelAndView() { //given UserFacade userFacade = mock(UserFacade.class); ModelAndView modelAndView = new ModelAndView(); given(userFacade.getTimezone()).willReturn("timezone X"); //when new UserDataInterceptor(userFacade) .postHandle(null, null, null, modelAndView); //then assertThat(modelAndView).constains("timezone", "timezone X"); }
  • 38. SRP for tests A test should have one and only one reason to fail. P.S. This is definitely a good advice for unit tests, but rather not valid for integration and end-to-end tests.
  • 39. Testing two things at once @DataProvider public Object[][] data() { return new Object[][] { {"48", true}, {"+48", true}, {"++48", true}, {"+48503", true}, {"+4", false}, {"++4", false}, {"", false}, {null, false}, {" ", false}, }; } @Test(dataProvider = "data") public void testQueryVerification(String query, boolean expected) { assertEquals(expected, FieldVerifier.isValidQuery(query)); }
  • 40. Testing two things at once @DataProvider public Object[][] data() { return new Object[][] { {"48", true}, {"+48", true}, Data {"++48", true}, {"+48503", true}, {"+4", false}, {"++4", false}, {"", false}, {null, false}, {" ", false}, }; } @Test(dataProvider = "data") Algorithm / Logic public void testQueryVerification(String query, boolean expected) { assertEquals(expected, FieldVerifier.isValidQuery(query)); }
  • 41. Testing two things at once @DataProvider public Object[][] data() { return new Object[][] { {"48", true}, {"+48", true}, {"++48", true}, {"+48503", true}, {"+4", false}, {"++4", false}, {"", false}, {null, false}, {" ", false}, }; } @Test(dataProvider = "data") public void testQueryVerification(String query, boolean expected) { assertEquals(expected, FieldVerifier.isValidQuery(query)); } testQueryVerification1() { assertEquals(true, FieldVerifier.isValidQuery(„48”)); } testQueryVerification2() { assertEquals(true, FieldVerifier.isValidQuery(„+48”)); } testQueryVerification3() { assertEquals(true, FieldVerifier.isValidQuery(„++48”)); } testQueryVerification4() { assertEquals(true, FieldVerifier.isValidQuery(„+48503”)); } ...
  • 42. Testing two things at once @DataProvider public Object[][] data() { return new Object[][] { {"48", true}, {"+48", true}, {"++48", true}, {"+48503", true}, {"+4", false}, {"++4", false}, {"", false}, {null, false}, {" ", false}, }; } @Test(dataProvider = "data") public void testQueryVerification(String query, boolean expected) { assertEquals(expected, FieldVerifier.isValidQuery(query)); }
  • 43. Concentrate on one feature @DataProvider public Object[][] validQueries() { return new Object[][] { {"48"}, {"48123"}, {"+48"}, {"++48"}, {"+48503"}}; } @Test(dataProvider = "validQueries") public void shouldRecognizeValidQueries( String validQuery) { assertTrue(FieldVerifier.isValidQuery(validQuery)); } @DataProvider public Object[][] invalidQueries() { return new Object[][] { {"+4"}, {"++4"}, {""}, {null}, {" "} }; } @Test(dataProvider = "invalidQueries") public void shouldRejectInvalidQueries( String invalidQuery) { assertFalse(FieldVerifier.isValidQuery(invalidQuery)); }
  • 45. Happy path testSum() { assertEquals(Math.sum(2,2), 4); } http://mw2.google.com/mw-panoramio/photos/medium/68775332.jpg
  • 46. Happy paths are for wimps 2 + 2 2 + -2 2 + -5 0 + 2 2 + 0 Integer.MAX_VALUE + something etc. http://kidskidskids.tumblr.com/post/1145294997
  • 47. Avoiding happy paths Start with one: testSum() { assertEquals(Math.sum(2,2), 4); } Do the simplest thing that works: sum(int x, int y) { return 4; } And then listen to your code. Because it tells you something. http://kidskidskids.tumblr.com/post/1145294997
  • 48. Avoiding happy paths sum(int x, int y) { return 4; } You moron! Your test is so pathetic, that I can make it pass by doing such a silly thing. Try harder! http://looneytunes09.files.wordpress.com/2010/07/lisa-yell.gif
  • 50. Who the heck is “user_2” ? @DataProvider public static Object[][] usersPermissions() { return new Object[][]{ {"user_1", Permission.READ}, {"user_1", Permission.WRITE}, {"user_1", Permission.REMOVE}, {"user_2", Permission.WRITE}, {"user_2", Permission.READ}, {"user_3", Permission.READ} }; }
  • 51. Ah, logged user can read and write... @DataProvider public static Object[][] usersPermissions() { return new Object[][]{ {ADMIN, Permission.READ}, {ADMIN, Permission.WRITE}, {ADMIN, Permission.REMOVE}, {LOGGED, Permission.WRITE}, {LOGGED, Permission.READ}, {GUEST, Permission.READ} }; }
  • 55. Do not make me learn the API! server = new MockServer(responseMap, true, new URL(SERVER_ROOT).getPort(), false);
  • 56. Do not make me learn the API! server = new MockServer(responseMap, true, new URL(SERVER_ROOT).getPort(), false); private static final boolean RESPONSE_IS_A_FILE = true; private static final boolean NO_SSL = false; server = new MockServer(responseMap, RESPONSE_IS_A_FILE, new URL(SERVER_ROOT).getPort(), NO_SSL);
  • 57. Do not make me learn the API! server = new MockServer(responseMap, true, new URL(SERVER_ROOT).getPort(), false); server = new MockServerBuilder() .withResponse(responseMap) .withResponseType(FILE) .withUrl(SERVER_ROOT) .withoutSsl().create();
  • 58. What is really important?
  • 59. What is really important? @DataProvider public Object[][] snapshotArtifacts() { return new Object[][]{ {"a", "b", "2.2-SNAPSHOT", Artifact.JAR }, {"c", "d", "2.2.4.6-SNAPSHOT", Artifact.JAR}, {"e", "f", "2-SNAPSHOT", Artifact.JAR} }; } @Test(dataProvider = "snapshotArtifacts") public void shouldRecognizeSnapshots( String groupId, String artifactId, String version, Type type) { Artifact artifact = new Artifact(groupId, artifactId, version, type); assertThat(artifact.isSnapshot()).isTrue(); }
  • 60. Only version matters @DataProvider public Object[][] snapshotVersions() { return new Object[][]{ {"2.2-SNAPSHOT"}, {"2.2.4.6-SNAPSHOT"}, {"2-SNAPSHOT"} }; } @Test(dataProvider = "snapshotVersions") public void shouldRecognizeSnapshots(String version) { Artifact artifact = new Artifact(VALID_GROUP, VALID_ARTIFACT_ID, version, VALID_TYPE); assertThat(artifact.isSnapshot()).isTrue(); }
  • 62. Naming is really important
  • 63. Test methods names are important • When test fails • Relation to focused tests
  • 64. Test methods names are important • testFindTransactionsToAutoCharge() • testSystemSuccess() • testOperation() @Test public void testOperation() { configureRequest("/validate") rc = new RequestContext(parser, request) assert rc.getConnector() == null assert rc.getOperation().equals("validate") }
  • 65. “should” is better than “test” • testOperation() • testQuery() • testConstructor() • testFindUsersWithFilter() • shouldRejectInvalidRequests() • shouldSaveNewUserToDatabase() • constructorShouldFailWithNegativePrice() • shouldReturnOnlyUsersWithGivenName()
  • 66. “should” is better than “test” • Starting test method names with “should” steers you in the right direction. http://jochopra.blogspot.com/ • “test” prefix makes your test method a limitless bag where you throw everything worth testing http://www.greenerideal.com/
  • 67. Test methods names are important @Test public void testQuery(){ when(q.getResultList()).thenReturn(null); assertNull(dao.findByQuery(Transaction.class, q, false)); assertNull(dao.findByQuery(Operator.class, q, false)); assertNull(dao.findByQuery(null, null, false)); List result = new LinkedList(); when(q.getResultList()).thenReturn(result); assertEquals(dao.findByQuery(Transaction.class, q, false), result); assertEquals(dao.findByQuery(Operator.class, q, false), result); assertEquals(dao.findByQuery(null, null, false), null); when(q.getSingleResult()).thenReturn(null); assertEquals(dao.findByQuery(Transaction.class, q, true).size(), 0); assertEquals(dao.findByQuery(Operator.class, q, true).size(), 0); assertEquals(dao.findByQuery(null, null, true), null); when(q.getSingleResult()).thenReturn(t); assertSame(dao.findByQuery(Transaction.class, q, true).get(0), t); when(q.getSingleResult()).thenReturn(o); assertSame(dao.findByQuery(Operator.class, q, true).get(0), o); when(q.getSingleResult()).thenReturn(null); assertSame(dao.findByQuery(null, null, true), null); }
  • 69. Assertion part is freaking huge public void shouldPreDeployApplication() { // given Artifact artifact = mock(Artifact.class); when(artifact.getFileName()).thenReturn("war-artifact-2.0.war"); ServerConfiguration config = new ServerConfiguration(ADDRESS, USER, KEY_FILE, TOMCAT_PATH, TEMP_PATH); Tomcat tomcat = new Tomcat(HTTP_TOMCAT_URL, config); String destDir = new File(".").getCanonicalPath() + SLASH + "target" + SLASH; new File(destDir).mkdirs(); // when tomcat.preDeploy(artifact, new FakeWar(WAR_FILE_LENGTH)); //then JSch jsch = new JSch(); jsch.addIdentity(KEY_FILE); Session session = jsch.getSession(USER, ADDRESS, 22); session.setConfig("StrictHostKeyChecking", "no"); session.connect(); Channel channel = session.openChannel("sftp"); session.setServerAliveInterval(92000); channel.connect(); ChannelSftp sftpChannel = (ChannelSftp) channel; sftpChannel.get(TEMP_PATH + SLASH + artifact.getFileName(), destDir); sftpChannel.exit(); session.disconnect(); File downloadedFile = new File(destDir, artifact.getFileName()); assertThat(downloadedFile).exists().hasSize(WAR_FILE_LENGTH); }
  • 70. Just say it public void shouldPreDeployApplication() { Artifact artifact = mock(Artifact.class); when(artifact.getFileName()) .thenReturn(ARTIFACT_FILE_NAME); ServerConfiguration config = new ServerConfiguration(ADDRESS, USER, KEY_FILE, TOMCAT_PATH, TEMP_PATH); Tomcat tomcat = new Tomcat(HTTP_TOMCAT_URL, config); tomcat.preDeploy(artifact, new FakeWar(WAR_FILE_LENGTH)); SSHServerAssert.assertThat(ARTIFACT_FILE_NAME) .existsOnServer(config).hasSize(WAR_FILE_LENGTH); }
  • 71. Asserting using private methods @Test public void testChargeInRetryingState() throws Exception { // given TxDTO request = createTxDTO(RequestType.CHARGE); AndroidTransaction androidTransaction = ... // when final TxDTO txDTO = processor.processRequest(request); // then assertState(request, androidTransaction, CHARGED, CHARGE_PENDING, AS_ANDROID_TX_STATE, ClientMessage.SUCCESS, ResultCode.SUCCESS); }
  • 72. Matchers vs. private methods assertState(TxDTO txDTO, AndroidTransaction androidTransaction, AndroidTransactionState expectedAndroidState, AndroidTransactionState expectedPreviousAndroidState, ExtendedState expectedState, String expectedClientStatus, ResultCode expectedRequestResultCode) { final List<AndroidTransactionStep> steps = new ArrayList<>(androidTransaction.getTransactionSteps()); final boolean checkPreviousStep = expectedAndroidState != null; assertTrue(steps.size() >= (checkPreviousStep ? 3 : 2)); if (checkPreviousStep) { AndroidTransactionStep lastStep = steps.get(steps.size() - 2); assertEquals(lastStep.getTransactionState(), expectedPreviousAndroidState); } final AndroidTransactionStep lastStep = steps.get(steps.size() - 1); assertEquals(lastStep.getTransactionState(), expectedAndroidState); assertEquals(lastStep.getMessage(), expectedClientStatus); assertEquals(txDTO.getResultCode(), expectedRequestResultCode); assertEquals(androidTransaction.getState(), expectedAndroidState); assertEquals(androidTransaction.getExtendedState(), expectedState); if (expectedClientStatus == null) { verifyZeroInteractions(client); } }
  • 73. Matchers vs. private methods @Test public void testChargeInRetryingState() throws Exception { // given TxDTO request = createTxDTO(CHARGE); AndroidTransaction androidTransaction = ... // when final TxDTO txDTO = processor.processRequest(request); // then assertThat(androidTransaction).hasState(CHARGED) .hasMessage(ClientMessage.SUCCESS) .hasPreviousState(CHARGE_PENDING) .hasExtendedState(null); assertEquals(txDTO.getResultCode(), ResultCode.SUCCESS); }
  • 74. What is asserted? @Test public void testCompile_32Bit_FakeSourceFile() { CompilerSupport _32BitCompilerSupport = CompilerSupportFactory.getDefault32BitCompilerSupport(); testCompile_FakeSourceFile(_32BitCompilerSupport); }
  • 75. What is asserted? @Test public void testCompile_32Bit_FakeSourceFile() { CompilerSupport _32BitCompilerSupport = CompilerSupportFactory.getDefault32BitCompilerSupport(); testCompile_FakeSourceFile(_32BitCompilerSupport); } private void testCompile_FakeSourceFile( CompilerSupport compilerSupport) { compiledFiles = compilerSupport.compile(new File[] { new File("fake") }); assertThat(compiledFiles, is(emptyArray())); }
  • 76. Asserting everything public void invalidTxShouldBeCanceled() { String fileContent = FileUtils.getContentOfFile("response.csv"); assertTrue(fileContent.contains( "CANCEL,123,123cancel,billing_id_123_cancel,SUCCESS,")); }
  • 77. Asserting everything public void invalidTxShouldBeCanceled() { String fileContent = FileUtils.getContentOfFile("response.csv"); assertTrue(fileContent.contains( "CANCEL,123,123cancel,billing_id_123_cancel,SUCCESS,")); } public void invalidTxShouldBeCanceled() { String fileContent = FileUtils.getContentOfFile("response.csv"); TxDTOAssert.assertThat(fileContent) .hasTransaction("123cancel").withResultCode(SUCCESS); }
  • 79. Asynchronous Calls @Test public void updatesCustomerStatus() throws Exception { // Publish an asynchronous event: publishEvent(updateCustomerStatusEvent); // Awaitility lets you wait // until the asynchronous operation completes: await() .atMost(5, SECONDS) .until(costumerStatusIsUpdated()); ... } http://code.google.com/p/awaitility/
  • 80. Expected exceptions @Test(expectedExceptions = SmsException.class) public void shouldThrowException() throws SmsException { try { String s = gutExtractor.extractGut(„invalid gut”); System.out.println(s); } catch (SmsException e) { e.printStackTrace(); throw e; } }
  • 81. Expected exceptions @Test(expectedExceptions = SmsException.class) public void shouldThrowException() throws SmsException { String s = gutExtractor.extractGut(„invalid gut”); }
  • 82. Expected exceptions (with catch-exception) @Test public void shouldThrowException() throws SmsException { when(gutExtractor.extractGut(„invalid gut”)); then(caughtException()) .isInstanceOf(SmsException.class) .hasMessage("Invalid gut") .hasNoCause(); } http://code.google.com/p/catch-exception/
  • 83. Running SUT's code concurrently @Test(threadPoolSize = 3, invocationCount = 10) public void testServer() { // this method will be run in parallel by 3 thread // 10 invocations (in total) }
  • 84. Dependent test methods @Test public void shouldConnectToDB() { // verifying that you can // estabilish a connection with DB } @Test(dependsOnMethods = „shouldConnectToDB”) public void should…() { // some operations on DB }
  • 85. Know your tools • Unit testing framework  Additional libraries  Use of temporary file rule  Hamcrest, FEST, Mockito,  Listeners catch-exception, awaitility, …  Concurrency • Build tool  @Before/@After  Parallel execution  Parametrized tests  CI  Test dependencies • IDE  Templates  Shortcuts
  • 86. What do you really want to test?
  • 87. What do you really want to test? @Test public void shouldAddAUser() { User user = new User(); userService.save(user); assertEquals(dao.getNbOfUsers(), 1); }
  • 88. You wanted to see that the number increased @Test public void shouldAddAUser() { int nb = dao.getNbOfUsers(); User user = new User(); userService.save(user); assertEquals(dao.getNbOfUsers(), nb + 1); }
  • 90. Doing it wrong public void myTest() { SomeObject obj = new SomeObject( a, b, c, productCode()); // testing of obj here } private String productCode(){ String[] codes = {"Code A", "Code B", "Code C", "Code D"}; int index = rand.nextInt(codes.length); return codes[index]; }
  • 91. The dream of stronger, random-powered tests public void myTest() { SomeObject obj = new SomeObject( randomName(), randomValue(), ....); // testing of obj here } Does it make your test stronger?
  • 92. The dream of stronger, random-powered tests public void myTest() { SomeObject obj = new SomeObject( randomName(), randomValue(), ....); // testing of obj here } Does it make your test stronger? ...or does it only bring confusion? Test failed Expected SomeObject(„a”, „b”, ....) but got SomeObject(„*&O*$NdlF”, „#idSLNF”, ....)
  • 94. There is more to it • Integration / end-to-end tests which are not parametrized (so they all try to set up jetty on port 8080), • Tests which should be really unit, but use Spring context to create objects, • Tests with a lot of dependencies between them (a nightmare to maintain!), • Tests which run slow • Tests which try to cover the deficiencies of production code and end up being a total mess, • Tests which verify methods instead of verifying responsibilities of a class • etc., etc.
  • 95. Test-last? No! • makes people not write tests at all • makes people do only happy-testing • tests reflect the implementation
  • 96. Always TDD? For six or eight hours spread over the next few weeks I struggled to get the first test written and running. Writing tests for Eclipse plug-ins is not trivial, so it’s not surprising I had some trouble. [...] In six or eight hours of solid programming time, I can still make significant progress. If I’d just written some stuff and verified it by hand, I would probably have the final answer to whether my idea is actually worth money by now. Instead, all I have is a complicated test that doesn’t work, a pile of frustration, eight fewer hours in my life, and the motivation to write another essay. Kent Beck, Just Ship, Baby
  • 97. Treat tests as the first class citizens • do it everyday or forget about it • make tests readable using matchers, • use the right tool for the job builders and good names • and learn to use it! • test behaviour not methods • do not live with broken windows • be pragmatic about the tests you write • respect KISS, SRP, DRY (?) • TDD always? • write good code, and you will also write • what is the best way to test it? good tests unit/integration/end-to-end ? • or rather write good tests and you • automate! will get good code for free • always concentrate on what is worth • code review your tests testing • do more than happy path testing • ask yourself questions like: 'is it really important that X should send • do not make the reader learn the API, message Y to Z?' make it obvious • use the front door – state testing before • bad names lead to bad tests interaction testing (mockc)
  • 99. Thank you! Thank you for watching these slides! You can learn more about wirting high quality tests by reading my book – „Practical Unit Testing with TestNG and Mockito”. You can also participate in writing of my new (free!) e-book devoted to bad and good tests.