December 02, 2009

Unit testing repository/DAO classes

In an enterprise application we have many layers. As part of testing a layer, we would Mock the underlying layer objects and test it. But to test the DAO layer, we need to write integration test to perform DB operations.

1) Declare Spring context aware Abstract Test class:
@ContextConfiguration(locations = {"classpath:data-applicationContext.xml"})  
 @TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)  
 @Transactional  
 public abstract class SpringTransactionTestContext extends AbstractTransactionalJUnit4SpringContextTests {  
 }  
2) Extend this class in all DAOTest and Autowire DAO:
 public class StudentDaoImplTest extends SpringTransactionTestContext {  
   @Autowired  
   private StudentDaoImpl personDao;  
 }  
3) Setup Test data:
If Student class is dependent on some School table, to test this class we need to insert School record. 
Options:
i) Using Session factory:
Use SessionFactory instance and save School object as shown below:

@Autowired  
 private SessionFactory sessionFactory;  
 School school= new School("School Name");  
 sessionFactory.getCurrentSession().save(school);  

Also, builders can be used to create objects with required test data. Look at this post on how to use builder pattern in Unit tests.

ii) Using SimpleJDBCTemplate - Less preferred:
SimpleJDBCTemplate object is found in . This can be directly used to run SQL scripts to insert into tables.
String schoolInsertQuery = "Insert into SCHOOL(ID,NAME) values (?,?)";  
 this.simpleJdbcTemplate.update(schoolInsertQuery, schoolId, schoolName);  

Once the Test execution is completed, it would automatically rollback the transaction (As defaultRollback is set true).

November 24, 2009

Picking up latest/highest record from a group using rank() function

Consider a example where we have Student, Subject and Mark information stored in different tables.

If we need to retrieve the toppers in every subject for a given year through SQL, the usual way is to use a Subquery, sort the result descending and pick up the ROWNUM=1 record.


Instead of doing all that, Oracle's rank() function can help us to achieve the same in a simple way. 

Table Structure:
- STUDENT (ID, Name, Class)
- SUBJECT (ID, Name, Year) 
- MARK (Subject_ID, Student_ID, Score)

SQL
 SELECT STUDENT.*, MARK.Mark, SUBJECT.Name  
 FROM STUDENT, SUBJECT,  
 (SELECT *, rank() OVER( partition by Subject_ID order by Score desc) rank FROM MARK )MARK  
 WHERE MARK.Student_ID=STUDENT.ID  
 AND MARK.Subject_ID=SUBJECT.ID  
 AND SUBJECT.Year=10  
 AND rank=1  
In the above example, instead of joining MARK table directly, we use Oracle rank() function to get the rank column also.

Here we partition the table by Subject_Id, apply the descending order by Score and then rank the result. When we add the WHERE clause on rank=1 it would fetch the toppers in every Subject.

This rank() function is also handy to get the latest record based on date from a given group.

Testing Hibernate list() DAO method which fetches all records.

Consider the example where you have a static table "VERSION" and the DAO method that fetches all records from VERSION table through Hibernate.

DAO Method:

   public List getAll() {  
     Criteria crit = getSession().createCriteria(Version.class);  
     crit.addOrder(Order.asc("name"));  
     return crit.list();  
   }  

Poor Test case:
Many occasions I have noticed Test cases that directly assert on the number of records being returned.
 public class VersionDaoImplTest extends AbstractTransactionalJUnit4SpringContextTests{  
   @Autowired  
   private VersionDao versionDao;  
   @Test  
   public void shouldReturnTheListOfVersions() {  
     List versions = versionDao.getAll();  
     assertEquals(5, versions.size());  
   }  
 }  

As seen above, this test case tries to assert Only 5 records are there in the Version Table. When a new record is added to this table, this test case would fail.

Proper test case:
Other way of testing this method is given below.
 @Test  
   public void shouldReturnTheListOfVersions() {    
   List versions = ratingsDao.versionDao.getAll();  
     assertNotNull(ratings);  
     int prevSize = ratings.size();  
     String insertSQL = "Insert into VERSION (NAME,VERSION_NO) values (?,?)";  
     this.simpleJdbcTemplate.update(insertSQL, "TST+", 95);  
     this.simpleJdbcTemplate.update(insertSQL, "TST", 96);  
     this.simpleJdbcTemplate.update(insertSQL, "TST-", 97);  
     versions = versionDao.getAll();  
     assertEquals(prevSize + 3, ratings.size());  
   }  

In this test case, we get the number of records currently in the database. We then insert 3 new records into the table and try to assert whether the number of records being returned has increased by 3.

Testing Sort Order:
Also to test the Criteria.addOrder(Order.asc("name")) you can enhance the test case with the following:
 for (int i = 1; i < versions.size(); i++) {  
       assertTrue(versions.get(i - 1).getName().compareTo(versions.get(i).getName()) < 0);  
 }  
This would assert that records are sorted by the NAME.

October 05, 2009

Spring MVC - Dynamic list binding and validating the input data


It took quiet some time to figure out how to display a dynamic list of data on screen and get user input information back. We also had to perform validation on this dynamic list input data and display error message on the corresponding row.  
Here is the simple example to achieve the above requirement.

Consider that you need to display Parent and list of child information on a screen and get the Age and Sex information of each child. Also you need to show error message on the screen if the user has not entered the age or sex information.

1) Form bean

public class MotherFormBean{  
   private String name;  
   private List<Child> children;  
   public MotherFormBean() {  
     children= LazyList.decorate(new ArrayList(),  
       FactoryUtils.instantiateFactory(Child.class));  
   }  
 }  
 public class Child{  
   private String name;  
   private String age;  
   private String sex;  
 }  

Instead of initializing the List as normal ArrayList, use the LazyList class (Commons-collection package). This is to avoid "IndexOutOfBoundException" when the data is submitted back to the controller.

2) JSP

<form:form action="${formAction}" name="childrenDetails">  
 <table><thead>  
  <tr>  
   <th><b>Name</b></th>  
   <th><b>Sex</b></th>  
   <th><b>Age</b></th>  
  </tr>  
  </thead>   
  <tbody>   
   <c:foreach items="${command.children}" var="child" varStatus="loop">   
    <tr>   
     <td><c:out value="${child.name}"></c:out>  
       <form:hidden path="children[${loop.index}].name"/>   
     </td>  
     <td><form:select path="children[${loop.index}].sex">  
        <option value="">--Please Select--</option>  
        <option value="M">Male</option>  
        <option value="F">Female</option>   
       </form:select>  
     </td>  
     <td><form:input path="children[${loop.index}].age"/></td>   
    </tr>  
   </c:foreach>   
  </tbody>   
  </table>  
 </form:form>  

This code will print all the children names and show input fields for Age and Sex field.

3) Validator Class

When the List data is submitted back, Spring constructs the data back to the Form bean with help of the LazyList collection. Validation and raising error on these fields is very simple.

 public class MotherBeanValidator implements org.springframework.validation.Validator {  
 .......   
   public void validate(Object target, Errors errors) {  
     MotherFormBean bean = (MotherFormBean) target;  
     List<Child> children = bean.getChildren();  
     for (int i = 0; i < children.size(); i++) {  
       if (StringUtils.isEmpty(children.get(i).getSex()))   
        errors.rejectValue("children[" + i + "].sex", "select.sex");  
       if (StringUtils.isEmpty(children.get(i).getAge()))   
        errors.rejectValue("children[" + i + "].age", "select.age");  
     }  
   }  
 }  

JSP Changes:
Include <form:errors> tag after the sex and age input fields.
eg.)
 <form:errors path="children[${loop.index}].sex"/>   
Hope this helps you in displaying the Collection data using Spring MVC and validating it.

Thanks to Matt Flemings blog for shedding some light on the LazyList.

September 13, 2009

Google Code Jam 2009 experience

   I participated in Google code Jam 2009 and tried to solve such problems for the first time in my life. As expected I could go only till Round 1 but it has been a great experience to participate in this event.

   It was amazing to see people solving the problems within few minutes which I had no idea where and how to start. I was particularly impressed by "ACRush" (Winner of Google Code Jam 2008) - before I could even understand the "The next number" problem in Round 1B he submitted the solution for it in 6 mins.  Google has again amazed me by providing such a platform for people to compete. The best part of it was making the solution submitted by all users available for download once the competition is over.  
  

   Having considerable professional experience in IT, I was hoping to fair better. But while attending the contest I felt that it had its own deterring factors. I was trying to visualize everything as Java objects and wasting time in modularizing code and naming it properly. Also having no exposure to such problems took its toll while racing against time. When I looked at the solutions from other users, most of them have used multi-dimensional arrays to solve the problems. Apart from my college days, I had almost never used multi-dimensional arrays in any of the professional projects. "Complex business logic" would typically involve complex queries and triggering different actions when an event occurs. We don't write algorithms or solve problems like these in a typical web application. Also maintaining state in multi-dimensional arrays is more prone to defect which would explain my lack of exposure to it.

  Another interesting thing about Google Code Jam is the competition statistics - by Country and Programming language. I was surprised to find that only 1220 contestants from India competed in the qualifiers second to China which had 1324 contestants. But in round one, only 6 from India had the "Perfect Score". I assume most of the university students are not aware of this competition. Google should promote this event better as it is a great platform for students to compete against the world's top coders.
Programming language: Java - 499 people but C++ - 1200 people. C++ has comfortably outnumbered Java as the preferred programming language in the code jam competition. Not happy to see this as a java developer :)  

I would never miss Google Code Jam competition again. I hope that I would fair better in 2010 competition. Congratulations to all the participants of the code jam and thanks to Google for providing this opportunity. Keep going.

August 26, 2009

Consultant - "Doing things quickly" - Not really

I was working as a consultant from an Indian IT company at a client location in Melbourne for almost three years. It was an exiting place to work as I was given opportunity try out new technologies for the project.

As a consultant, I had the following perspective on my role and obliged with it:
  • I had to quickly learn the tools and technologies used at my client place (like., Weblogic Portal, Spring, IBatis, Clearcase, etc) 
  • I had to do my job quickly to add value to my customer.
  • I had to understand the project functionalities quickly.
  • I had to adapt to new technologies quickly and start contributing.
But only when my clients hired three consultants from ThoughtWorks, I realized how little was my value add to the customer apart from my "doing things quickly".

I had gravely missed to think on these lines as a consultant...

1. Are we using the right tools?  Are there better tools out there? Clearcase version control - it is being used at my client place for more than 5 years. After the initial hiccups, I got used to it and tried to solve our problems within the tools limits. I never had a clue that this could be challenged. But TW, proposed Subversion as an alternate for development environment and integrated with Subversion to Clearcase behind the scene. Introduction of this SVN to the Test driven development environment is claimed to have helped the developers is refactoring the code at ease.

2. Are we using the right technology? Can it be done in a simple way with other technology?
Weblogic Portal was traditionally used at my client place for more than five years. It is a good technology to work on if you are familiar with it. It had so many features and good IDE to develop things.
But we had a big learning curve with Weblogic portal in our first project and it has always been difficult for new developers coming into project. Few developers tried proposing alternate technologies but was not able to convince everyone.
Again TW consultants, annoyed with the time taken to learn,build and deploy portal applications, proposed Spring MVC as the alternate technology. But this time they presented it with a case study comparing the effort  involved in developing a functionality in Weblogic portal and Spring MVC. As it directly translated to $$$ terms, it caught the attention of everyone and Spring MVC was inducted into clients technology stack.

3. Are we up-to-date with technology?
Being up-to-date with technology not only helps in doing things in a smarter way but also leads to doing things quicker. I am still wondering how few people are so familiar with the latest open source technologies. For example, which tool will you use for XML processing?

  • Do not choose the opensource library just because you have used it earlier.
  • Look out for better opensource libraries which will help you to achieve the result in a smart way. 
  • Be up-to-date about the new libraries.
I never imagined that a technology like "Weblogic Portal" which was considered as the standard for all applications could be replaced. I never thought a tool like "Clearcase" which is being used for 5 years can have an alternate.The lesson I learnt from this experience is "Never take anything for granted". 

I always thought that I had to learn the technologies used by the client but I have now realized that it is also my duty to identify what is right for the client.