Total Pageviews

Showing posts with label Project Lombok. Show all posts
Showing posts with label Project Lombok. Show all posts

2017/08/10

How to Improve Code Coverage After Using Apache Lombok?

Problem
After using Project Lombok, we can find out it help use reduce our boilerplate 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
package albert.practice.lambda;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import com.google.common.base.Strings;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

public class LambdaTest {

    public List<Person> filterData() {
        List<Person> people = createData();
        return people.stream().filter(p -> p.age >= 35).filter(p -> !Strings.isNullOrEmpty(p.email))
                .collect(Collectors.toList());
    }

    public List<Person> filterDataWithSort() {
        List<Person> people = createData();
        return people.stream().filter(p -> p.age >= 35).sorted((p1, p2) -> (p1.age - p2.age))
                .sorted((p1, p2) -> p2.name.compareTo(p1.name)).collect(Collectors.toList());
    }

    public List<Person> createData() {
        Person ben = new Person("Ben Whittaker", 70, "ben@gmail.com");
        Person jules = new Person("Jules Ostin", 30, "jules@gmail.com");
        Person fiona = new Person("Fiona Farwell", 68, null);
        Person matt = new Person("Matt", 35, "matt@gmail.com");
        
        return Arrays.asList(ben, jules, fiona, matt);
    }

    @Data
    @ToString
    @AllArgsConstructor
    @NoArgsConstructor
    public static class Person {
        private String name;
        private int age;
        private String email;
    }

}

But you will also notice your code coverage rate is down dramatically.


How to improve the code coverage rate?


How-To
Add two dependencies to your pom.xml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<dependencies>
    <dependency>
        <groupId>org.meanbean</groupId>
        <artifactId>meanbean</artifactId>
        <version>2.0.2</version>
    </dependency>

    <dependency>
        <groupId>nl.jqno.equalsverifier</groupId>
        <artifactId>equalsverifier</artifactId>
        <version>2.2.1</version>
        <scope>test</scope>
    </dependency>
</dependencies>

Check the testPersonBean() and testEqualsAndHashCode() in this test case:
 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
package albert.practice.lambda;

import static org.junit.Assert.assertEquals;

import java.util.List;

import org.junit.Before;
import org.junit.Test;
import org.meanbean.test.BeanTester;

import albert.practice.lambda.LambdaTest.Person;
import lombok.extern.slf4j.Slf4j;
import nl.jqno.equalsverifier.EqualsVerifier;
import nl.jqno.equalsverifier.Warning;

@Slf4j
public class LambdaUnitTest {

    private LambdaTest lambdaTest = null;

    @Before
    public void setup() {
        lambdaTest = new LambdaTest();
    }

    @Test
    public void testCreateData() {
        List<Person> people = lambdaTest.createData();
        assertEquals(people.size(), 4);
    }

    @Test
    public void testFilterData() {
        List<Person> people = lambdaTest.filterData();
        log.debug("testFilterData = " + people.toString());
        assertEquals(people.size(), 2);
    }

    @Test
    public void testFilterDataWithSort() {
        List<Person> people = lambdaTest.filterDataWithSort();
        log.debug("testFilterDataWithSort = " + people.toString());
        assertEquals(people.size(), 3);
    }

    // Testing getter and setters methods
    @Test
    public void testPersonBean() {
        new BeanTester().testBean(LambdaTest.Person.class);
    }

    // Testing equals() and hashCode()
    @Test
    public void testEqualsAndHashcode() {
        EqualsVerifier.forClass(LambdaTest.Person.class)
                .suppress(Warning.STRICT_INHERITANCE, Warning.NONFINAL_FIELDS).verify();
    }

}

After running code coverage analysis, you can find out this problem had been solved:




Reference
[1] http://www.jdev.it/tips-unit-testing-javabeans/

2017/08/03

[Lombok] Utilize @Builder annotation to produce complex builder APIs for your classes

Problem
If you would like to apply builder pattern in your Java class, the class will looks like:
package albert.practice.designPattern.builder;

public class User {

    private String firstName;
    private String lastName;
    private String email;

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public String getEmail() {
        return email;
    }

    private User(UserBuilder builder) {
        this.firstName = builder.firstName;
        this.lastName = builder.lastName;
        this.email = builder.email;
    }

    @Override
    public String toString() {
        return "User [firstName=" + firstName + ", lastName=" + lastName + ", email=" + email + "]";
    }

    public static class UserBuilder {
        private String firstName;
        private String lastName;
        private String email;

        public UserBuilder firstName(String firstName) {
            this.firstName = firstName;
            return this;
        }

        public UserBuilder lastName(String lastName) {
            this.lastName = lastName;
            return this;
        }

        public UserBuilder email(String email) {
            this.email = email;
            return this;
        }

        public User build() {
            User user = new User(this);
            return user;
        }
    }
}


Compared with POJO class, you may notice the number of lines of code increase at least to double in builder pattern. Does it has any convenient approach to implement and yield the same benefit which builder pattern bring for us?

How-To
You can make good use of @Builder annotation which provide by Project Lombok. The updated code look like:
package albert.practice.designPattern.builder;

import lombok.Builder;
import lombok.Getter;
import lombok.ToString;

/*
 * The @Builder annotation produces complex builder APIs for your classes.
 * @Builder lets you automatically produce the code required to have your class be instantiable with code such as:
 * Person.builder().name("Adam Savage").city("San Francisco").job("Mythbusters").job("Unchained Reaction").build();
 */
@Builder
@Getter
@ToString
public class User2 {
    
    private String firstName;
    private String lastName;
    private String email;

}


Here is the test code:
package albert.practice.designPattern.builder;

import static org.junit.Assert.assertNotNull;

import org.junit.Test;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class UserTest {

    @Test
    public void testUser() {
        User user = new User.UserBuilder().firstName("Albert").lastName("Kuo")
                .email("test@gmail.com").build();
        log.debug("user = " + user.toString());
        assertNotNull(user);
    }

    @Test
    public void testUser2() {
        User2 user2 = new User2.User2Builder().firstName("Albert").lastName("Kuo")
                .email("test@gmail.com").build();
        log.debug("user2 = " + user2.toString());
        assertNotNull(user2);
    }

}



Maven dependencies are as bellows:
  <dependencies>
        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.8</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.21</version>
        </dependency>

        <!-- logback -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.1.7</version>
        </dependency>

        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.1.7</version>
        </dependency>
    </dependencies>

Reference
[1] https://projectlombok.org/features/Builder.html



2016/10/08

[Project Lombok] @Cleanup

Project Lombok provide an easy way to ensure a given resource is automatically cleaned up before the code execution path exits your current scope. All you need to do is to annotate any local variable declaration with the @Cleanup annotation.


As-Is (Do not annotate @Cleanup)
 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
private void readExcelTest1(String excelFile) throws IOException {
     List<Issue> issues = new ArrayList<Issue>();

     InputStream inputStream = null;
     Workbook workbook = null;

     try {
         // 1. Create a Workbook.
         inputStream = new FileInputStream(new File(excelFile));

         // 2. Get first sheet
         workbook = new HSSFWorkbook(inputStream);

         Sheet sheet = workbook.getSheetAt(0);

         // 3. Iterate through each rows from first sheet
         Iterator<Row> rowIterator = sheet.iterator();
         int rowCount = 1;
         while (rowIterator.hasNext()) {
            // (1) ignore header row
            if (rowCount == 1) {
              rowIterator.next();
              rowCount++;
             }
             // (2) start to read each row from second row
             else {
                 Row row = rowIterator.next();
                 Integer id = Double.valueOf(row.getCell(0).getNumericCellValue()).intValue();
                 String subject = row.getCell(1).getStringCellValue();
                 String status = row.getCell(2).getStringCellValue();
                 String priority = row.getCell(3).getStringCellValue();
                 String notes = row.getCell(4).getStringCellValue();

                 Issue issue = new Issue();
                 issue.setId(id);
                 issue.setSubject(subject);
                 issue.setStatus(status);
                 issue.setPriority(priority);
                 issue.setNotes(notes);

                 issues.add(issue);
              }
           }

          // print collection
          issues.stream().forEach(issue -> log.debug(issue.toString()));
     } catch (FileNotFoundException e) {
         throw e;
     } catch (IOException e) {
         throw e;
     } finally {
         if (workbook != null) {
           workbook.close();
         }

         if (inputStream != null) {
           inputStream.close();
         }
     }
}



To-Be(Annotte with @Cleanup)
It will ensure the variable declaration that you annotate will be cleaned up by calling its close method, regardless of what happens. Implemented by wrapping all statements following the local variable declaration to the end of your scope into a try block that, as a finally action, closes the resource. 
 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
 private void readExcelTest2(String excelFile) throws Exception {
     try {
         // 1. Create a Workbook.
         @Cleanup
         InputStream inputStream = new FileInputStream(new File(excelFile));

         // 2. Get first sheet
         @Cleanup
         Workbook workbook = new HSSFWorkbook(inputStream);

         List<Issue> issues = new ArrayList<Issue>();

         Sheet sheet = workbook.getSheetAt(0);
 
         // 3. Iterate through each rows from first sheet
         Iterator<Row> rowIterator = sheet.iterator();
         int rowCount = 1;
         while (rowIterator.hasNext()) {
              // (1) ignore header row
              if (rowCount == 1) {
              rowIterator.next();
              rowCount++;
              }
              // (2) start to read each row from second row
              else {
                  Row row = rowIterator.next();
                  Integer id = Double.valueOf(row.getCell(0).getNumericCellValue()).intValue();
                  String subject = row.getCell(1).getStringCellValue();
                  String status = row.getCell(2).getStringCellValue();
                  String priority = row.getCell(3).getStringCellValue();
                  String notes = row.getCell(4).getStringCellValue();

                  Issue issue = new Issue();
                  issue.setId(id);
                  issue.setSubject(subject);
                  issue.setStatus(status);
                  issue.setPriority(priority);
                  issue.setNotes(notes);
    
                  issues.add(issue);
              }
         }

         // print collection
         issues.stream().forEach(issue -> log.debug(issue.toString()));
    
     } catch (FileNotFoundException e) {
         throw e;
     } catch (Exception e) {
         throw e;
     }
 }



Reference
[1] https://projectlombok.org/features/Cleanup.html

2016/05/06

Using Project Lombok to eliminate boilerplate code

What is boilerplate code
Boilerplate is the term used to describe sections of code that have to be included in many places with little or no alteration. It is more often used when referring to languages which are considered verbose, i.e. the programmer must write a lot of code to do minimal jobs.

How-to
We can use @Data to generate getters for all fields:
Before:
 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
package albert.practice.xstream.beans;

import java.io.Serializable;

public class Doc implements Serializable {

    private static final long serialVersionUID = 1L;

    // 訂單編號
    private String regId;

    // 要保人ID
    private String ownerId;

    // 保單號碼
    private String policyNumber;

    // 被保人 ID
    private String insuredId;

    // 表單代碼
    private String formId;

    public String getRegId() {
        return regId;
    }

    public void setRegId(String regId) {
        this.regId = regId;
    }

    public String getOwnerId() {
        return ownerId;
    }

    public void setOwnerId(String ownerId) {
        this.ownerId = ownerId;
    }

    public String getPolicyNumber() {
        return policyNumber;
    }

    public void setPolicyNumber(String policyNumber) {
        this.policyNumber = policyNumber;
    }

    public String getInsuredId() {
        return insuredId;
    }

    public void setInsuredId(String insuredId) {
        this.insuredId = insuredId;
    }

    public String getFormId() {
        return formId;
    }

    public void setFormId(String formId) {
        this.formId = formId;
    }

}

After using @Data:
 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
package albert.practice.xstream.beans;

import java.io.Serializable;

import lombok.Data;

@Data
public class Doc implements Serializable {

    private static final long serialVersionUID = 1L;

    // 訂單編號
    private String regId;

    // 要保人ID
    private String ownerId;

    // 保單號碼
    private String policyNumber;

    // 被保人 ID
    private String insuredId;

    // 表單代碼
    private String formId;

}


We can use @NoArgsConstructor to generate a no-args constructor.
 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
package albert.practice.xstream.beans;

import java.io.Serializable;

import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
public class Doc implements Serializable {

    private static final long serialVersionUID = 1L;

    // 訂單編號
    private String regId;

    // 要保人ID
    private String ownerId;

    // 保單號碼
    private String policyNumber;

    // 被保人 ID
    private String insuredId;

    // 表單代碼
    private String formId;

}

We can use @AllArgsConstructor to generate an all-args constructor
 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
package albert.practice.xstream.beans;

import java.io.Serializable;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Doc implements Serializable {

    private static final long serialVersionUID = 1L;

    // 訂單編號
    private String regId;

    // 要保人ID
    private String ownerId;

    // 保單號碼
    private String policyNumber;

    // 被保人 ID
    private String insuredId;

    // 表單代碼
    private String formId;

}

We can use @ToString generate an implementation for the toString method inherited by all objects, consisting of printing the values of relevant fields. 
 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
package albert.practice.xstream.beans;

import java.io.Serializable;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Doc implements Serializable {

    private static final long serialVersionUID = 1L;

    // 訂單編號
    private String regId;

    // 要保人ID
    private String ownerId;

    // 保單號碼
    private String policyNumber;

    // 被保人 ID
    private String insuredId;

    // 表單代碼
    private String formId;

}

Reference
[1] http://jnb.ociweb.com/jnb/jnbJan2010.html
[2] https://en.wikipedia.org/wiki/Boilerplate_%28text%29