Tuesday, 23 May 2017

This week 10/2017 - TestNG vs JUnit

In this article I'd like to compare two test Java framework. I have always used JUnit with some third party libraries but I didn't know all JUnit features. In this article I will list them all (all described on JUnit web side).

JUnit was probably the first Java unit test framework (I don't know any which has its beginning in 2002. Then in 2004 Cédric Beust frustrated of JUnit deficiencies, has created TestNG. In this article refers to JUnit (v 4.12) and TestNG (v 6.10) but new version of JUnit is in progress.

Both frameworks are dedicated to create unit, functional and integration tests but with TestNG it is all possible without any third party libraries. TestNG is more configurable in case of grouping and ordering of test execution.

The main reason why TestNG was created is that creator of TestNG was frustrated that JUnit creates a new instance of class for each test and he changed this approach in the TestNG to create one instance of a test class for each test methods in this class. This has some consequences - JUnit class is state full - contains class attributes which are refreshed before each test method. In case of TestNG class's attributes share some state for all test methods in a class. This assumption allows to create in an easy way test sequence.

Both frameworks allows to categorise and group tests. However the TestNG  is much more flexible and allows to create some sequence of tests.

Bellow I gathered most important advantages of each framework and attached some simple examples of use it.


TestNG (v 6.10)
1. Dedicated for unit, functional, integration tests.
2. Make dependences between methods and groups in one class.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
@Test()
public void testZAdd() throws Exception {
    calc.addMemory(BigDecimal.TEN);
    Assert.assertEquals(calc.getMemory(), BigDecimal.TEN);
}

@Test(dependsOnMethods = {"testZAdd"})
public void testAdd2() throws Exception {
    calc.addMemory(BigDecimal.TEN);
    Assert.assertEquals(calc.getMemory(), new BigDecimal(20));
}

@Test(dependsOnMethods = {"testZAdd", "testAdd2"})
public void testClean() throws Exception {
    Assert.assertEquals(calc.getMemory(), new BigDecimal(20));
    calc.cleanMemory();
    Assert.assertEquals(calc.getMemory(), new BigDecimal(0));
}

3. One class can be supported by one thread

4. To organise all test in a testng.xml file - something like suite case in JUnit

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<test name="MyTestGroup">
  <groups>
    <run>
      <exclude name="brokenTests"  />
      <include name="checkinTests"  />
    </run>
  </groups>
  
  <classes>
    <class name="test.IndividualMethodsTest">
      <methods>
        <include name="testMethod" />
      </methods>
    </class>
  </classes>
</test>

5. Support input parameters of test method.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@DataProvider(name = "test1")
public Object[][] createData1() {
    return new Object[][] {
            { null, null, BigDecimal.ONE, BigDecimal.TEN},
            { BigDecimal.TEN, new BigDecimal(2), new BigDecimal(4), new BigDecimal(4)},
    };
}

@DataProvider(name = "test2")
public Object[][] createData2() {
    return new Object[][] {
            { null, null, BigDecimal.ONE, BigDecimal.TEN},
            { new BigDecimal(12), new BigDecimal(2), new BigDecimal(2), new BigDecimal(3)},
    };
}

@org.testng.annotations.Test(dataProvider = "test1")
public void testAdd(BigDecimal sum, BigDecimal a, BigDecimal b, BigDecimal c) throws Exception {
    Assert.assertEquals(ComplexOperations.add(a, b, c), sum);
}

@org.testng.annotations.Test(dataProvider = "test2")
public void testMultiply(BigDecimal sum, BigDecimal a, BigDecimal b, BigDecimal c) throws Exception {
    Assert.assertEquals(ComplexOperations.multiply(a, b, c), sum);
}

6. Reusable of methods and classes.
7. Support parallel testing: per class, method, instance, etc. - It is possible to execute one method in parallel many times, ex. 60 times in 10 threads.


1
2
3
4
5
@Test(dependsOnMethods = {"testClean"}, threadPoolSize = 10, invocationCount = 60, timeOut = 1000)
public void testAfterClean() throws Exception {
    System.out.println("To " + Thread.currentThread().getId() + " -> " + this);
    Assert.assertTrue(true);
}

or by test.xml


1
<suite name="My suite" parallel="classes" thread-count="5">

8. Inject dependences alone or by an external library ex. by Guice


JUnit (v. 4.12)
1. Categorisation of test ??class or method??
2. Parametrisation of test but test class instance have to be statefull.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@RunWith(Parameterized.class)
public class ComplexOperationsJUnitDataProvider {

    private BigDecimal sum;
    private BigDecimal a;
    private BigDecimal b;
    private BigDecimal c;

    @Parameterized.Parameters
    public static Object[][] createData1() {
        return new Object[][]{
                {null, null, BigDecimal.ONE, BigDecimal.TEN},
                {BigDecimal.TEN, new BigDecimal(2), new BigDecimal(4), new BigDecimal(4)},
        };
    }

    public ComplexOperationsJUnitDataProvider(BigDecimal sum, BigDecimal a, BigDecimal b, BigDecimal c) {
        this.a = a;
        this.b = b;
        this.c = c;
        this.sum = sum;
    }

    @Test
    public void testAdd() throws Exception {
        Assert.assertEquals(ComplexOperations.add(a, b, c), sum);
    }
}

3. Basic test ordering by @FixMethodOrder(MethodSorters.NAME_ASCENDING)
4. Assumption to tested code. If assumptions is false then skip test.


1
2
3
4
5
6
@Test
public void testAdd1() throws Exception {
    Assume.assumeThat(calc.getMemory(), is(BigDecimal.ZERO));
    calc.addMemory(BigDecimal.TEN);
    Assert.assertEquals(calc.getMemory(), new BigDecimal(10));
}

5. Continues testing - external library. - This solution execute a test in background after it discover change in code.
6. Concurrent test - ConcurrentUnit
7. Ignore test

1
2
3
@Ignore
@Test
public void testMyAlgorythm(){ ... }

Test class with 2 test methods, one with Ignore annotation, gives following result
Tests run: 2, Failures: 0, Errors: 0, Skipped: 1


8. Timeout for test

1
2
@Test(timeout = 10)
public void testMyAlgorythm() { ...}

9. Rules in test class body
10. @Theory mechanism - helps with generation of input data


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@RunWith(Theories.class)
public class CalculatorWithMemoryJUnit {

    private CalculatorWithMemory calc = new CalculatorWithMemory();

    @Theory
    public void testSum(@TestedOn(ints = {0, 10, 30, 60}) int val, @TestedOn(ints = {0, 10, 30, 60}) int sum) {
        Assume.assumeThat(val, is(sum));
        System.out.println("Test with val " + val + ", sum=" + sum);
        Assume.assumeThat(calc.getMemory(), is(BigDecimal.ZERO));
        calc.addMemory(new BigDecimal(val));
        Assert.assertEquals(0, new BigDecimal(sum).compareTo(calc.getMemory()));
    }
}

Next time I am going to present mock frameworks.



Wednesday, 3 May 2017

This week 9/2017 - The REST client API

There are a few implementation of REST client for Java. I will compare 3, I think, most popular implementations.
  1. Jersey (v. 2.25.x)
  2. Spring Web (v. 3.2.x)
  3. Apache CXF (v. 3.0.x)
My goal is to create component which can:
  1. Connect by secure channel - SSL
  2. Authentication bu Basic authorization mechanism.
  3. Send custom message.
  4. Read response and deserialize it into object.
So most common use of client. I was pointing to: flexible interface, clean code - nothing special. So I created following Java code


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import java.util.Arrays;
import java.util.Base64;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.WebTarget;
import org.apache.cxf.jaxrs.client.WebClient;
import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.RestTemplate;

public class App {

 // Apache CXF
 private static WebClient createWebClient() {
  return WebClient.create(getUrl(), "aa", "bbc", null);
 }
 private void testWebClient(){
  System.out.println("\n\ntestWebClient:");
//  System.out.println(createWebClient().path("todo22").accept(MediaType.TEXT_XML).get(Todo2.class));
  System.out.println(createWebClient().path("todo22").get(Todo2.class));
  System.out.println(createWebClient().path("todo22").get(String.class));
 }

 // Jersey
 private static Client createWebTarget() {
  HttpAuthenticationFeature authenticationFeature = HttpAuthenticationFeature.basic("aa", "bbc");
  return ClientBuilder.newBuilder().newClient().register(authenticationFeature);
 }

 private void testWebTarget(){
  WebTarget service = createWebTarget().target(getUrl());
  System.out.println("\n\ntestWebTarget :");
//  System.out.println(service.path("todo22").request(MediaType.TEXT_XML).get(Todo2.class));
  System.out.println(service.path("todo22").request().get(Todo2.class));
  System.out.println(service.path("todo22").request().get(String.class));
 }

 // Spring Web Rest
 private static HttpHeaders getHeaders(){
  String plainCredentials="aa:bbc";
  String base64Credentials = new String(Base64.getEncoder().encode(plainCredentials.getBytes()));

  HttpHeaders headers = new HttpHeaders();
  headers.add("Authorization", "Basic " + base64Credentials);
  headers.setAccept(Arrays.asList(org.springframework.http.MediaType.TEXT_XML));
  return headers;
 }

 private void testRestTemplate(){
  System.out.println("\n\ntestRestTemplate :");
  RestTemplate restTemplate = new RestTemplate();

  // restTemplate.getMessageConverters().add(new MappingJacksonHttpMessageConverter());
  restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
  HttpEntity<String> request = new HttpEntity<String>(getHeaders());
  System.out.println(restTemplate.exchange(getUrl() + "todo22", HttpMethod.GET, request, Todo2.class).getBody());
  System.out.println(restTemplate.exchange(getUrl() + "todo22", HttpMethod.GET, request, String.class).getBody());
 }

In my opinion the simplest interface has Apache CXF but you need to remember to create new instance for multiple services.
Jersey interface looks to be it most powerful. At the beginning there is created some service client template - core of client. Then every time when path method is executed there is created new instance of client with resource name.
Jersey support all kind of authentication methods providing tool. It is opposite to Spring Web RestTemplate where I had to prepare manually authorization header. Spring Web REST required much more code then two other solutions.

Resources:

This week 8/2017

In this post I will write two small tips.

Slf4J - has one handy feature. One interface solves two problems:
1) firstly it check if message will be used, then transform parameters to String type and build all message,
2) helps to have clean code without concatenation of parameters

LOGGER.debug("Test message {} {} {}{}{}{}{}{}{}", 3, "+", 3, =, null, " 6");

Earlier I use to check logging level manually and use to format messages by String::printf


Lombok - builder pattern is great pattern to create immutable value object with many attributes. Lombok does it for you, you have only to add @Builder annotation. Unfortunately it has other standard of calling setter method (there is no set prefix) and it is useless to create builder for inheritance’s class.

Bellow I compare simple class with lombok annotations and equivalent to it.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import lombok.Builder;
import lombok.Data;
import lombok.NonNull;

@Data
@Builder
public class MyLoombok {

 @NonNull
 private final String attr1;

 @NonNull
 private final int attr2;
}


Generated code of builder.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
import lombok.NonNull;

public class MyLoombok {

 @NonNull
 private final String attr1;

 @NonNull
 private final int attr2;

 @java.beans.ConstructorProperties({"attr1", "attr2"})
 MyLoombok(String attr1, int attr2) {
  this.attr1 = attr1;
  this.attr2 = attr2;
 }

 public static MyLoombokBuilder builder() {
  return new MyLoombokBuilder();
 }

 @NonNull
 public String getAttr1() {
  return this.attr1;
 }

 @NonNull
 public int getAttr2() {
  return this.attr2;
 }

 public boolean equals(Object o) {
  if (o == this) return true;
  if (!(o instanceof MyLoombok)) return false;
  final MyLoombok other = (MyLoombok) o;
  if (!other.canEqual((Object) this)) return false;
  final Object this$attr1 = this.getAttr1();
  final Object other$attr1 = other.getAttr1();
  if (this$attr1 == null ? other$attr1 != null : !this$attr1.equals(other$attr1)) return false;
  if (this.getAttr2() != other.getAttr2()) return false;
  return true;
 }

 public int hashCode() {
  final int PRIME = 59;
  int result = 1;
  final Object $attr1 = this.getAttr1();
  result = result * PRIME + ($attr1 == null ? 43 : $attr1.hashCode());
  result = result * PRIME + this.getAttr2();
  return result;
 }

 protected boolean canEqual(Object other) {
  return other instanceof MyLoombok;
 }

 public String toString() {
  return "singleclass.MyLoombok(attr1=" + this.getAttr1() + ", attr2=" + this.getAttr2() + ")";
 }

 public static class MyLoombokBuilder {
  private String attr1;
  private int attr2;

  MyLoombokBuilder() {
  }

  public MyLoombok.MyLoombokBuilder attr1(String attr1) {
   this.attr1 = attr1;
   return this;
  }

  public MyLoombok.MyLoombokBuilder attr2(int attr2) {
   this.attr2 = attr2;
   return this;
  }

  public MyLoombok build() {
   return new MyLoombok(attr1, attr2);
  }

  public String toString() {
   return "singleclass.MyLoombok.MyLoombokBuilder(attr1=" + this.attr1 + ", attr2=" + this.attr2 + ")";
  }
 }
}