Appendix: Test Driven Development with Sculptor

                                     

Appendix: Test Driven Development with Sculptor

This is an appendix to the article Improving Developer Productivity with Sculptor 

Introduction

Sculptor doesn't mandate any specific development methodology, but personally I prefer Test Driven Development with evolutionary design and refactoring. It is important to explain that Sculptor can be combined with an agile mindset and Test Driven Development (TDD) in particular. Using this approach the DSL model is not a big design up front.

Unit tests of Sculptor artifacts can be done for Domain Objects, Repositories, Services and Consumers.

Domain Objects can be tested as ordinary POJOs with JUnit, exactly as usual. It is probably the behavior that needs to be tested and methods for that are added manually anyway.

When testing Repositories, Services and Consumers it is convenient to use DBUnit to load the in memory HSQLDB database with test data from an XML file. The database is recreated for each test method.

Sometimes it is better to test the Services and Consumers by stubbing dependencies to Repositories and other Services.

TDD Example

To explain the TDD approach we will look at an example. It is the same example application as described in the article. Assume we need to add a service to lookup a library with a specific name.

  1. Add test method. Write asserts and call the non-existing service method.

        public void testFindLibraryByName() {
            String name = "famous";
            Library library = libraryService.findLibraryByName(
                    getServiceContext(), name);
            assertNotNull(library);
            assertEquals(name, library.getName());
        }					
    				
  2. Add test data in the DBUnit XML file.

    <?xml version='1.0' encoding='UTF-8'?>
    <dataset>
    	<LIBRARY ID="1" NAME="famous" VERSION="1"/>
    </dataset>
    				
  3. Thereafter you have a good feeling of the API of the method. Add it to the DSL model.

            Service LibraryService {
              @Library findLibraryByName(String name);
              saveLibrary delegates to LibraryRepository.save;
              findMediaByName delegates to MediaRepository.findMediaByName;
              findMediaByCharacter delegates to 
                MediaRepository.findMediaByCharacter;
              findPersonByName delegates to PersonService.findPersonByName;
            }
    				
  4. Generate code with with mvn generate-sources. When you add new methods it sometimes results in compilation errors in the hand written classes, which are only generated once. In this case LibraryServiceImpl. These types of compilation errors can easily be fixed with Eclipse ctrl+1 Add unimplemented methods.

  5. Now the compilation error in the test class has disapeared. Run the test to see red bar.

  6. Implement the method, by adding hand written code and/or adding more stuff to the DSL model. In this case we add a generic findByCondition operation in the Repository. We also add a dependency injection of the Repository in the Service.

            Service LibraryService {
              inject LibraryRepository
              @Library findLibraryByName(String name);
              saveLibrary delegates to LibraryRepository.save;
              findMediaByName delegates to MediaRepository.findMediaByName;
              findMediaByCharacter delegates to 
                MediaRepository.findMediaByCharacter;
              findPersonByName delegates to PersonService.findPersonByName;
            }
       
            Entity Library {
              String name key
              reference Set<@PhysicalMedia> media opposite library
              
              Repository LibraryRepository {
                findByCondition;
                findById;
                save;
                findByQuery;
              }
            }
    				

    Add some hand written code in LibraryServiceImpl to call findByCondition.

        public Library findLibraryByName(ServiceContext ctx, String name) {
            List<ConditionalCriteria> criteria = 
                criteriaFor(Library.class).withProperty(name()).eq(name).build();
            List<Library> libraries = getLibraryRepository().findByCondition(criteria);
            return libraries.get(0);
        }
    				
  7. Run test. Green bar!

  8. Continue in the same way by adding another test method, e.g. for the failure scenario when a matching library is not found.

  9. Refactor if necessary.

Refactoring

Refactoring can be done like this. Use the ordinary refactoring tools in Eclipse, which will modify the generated code also. Run tests to make sure you still have green bar. Thereafter you do the corresponding changes in the DSL model and generate with mvn generate-sources. Run tests.

Before generating, all files in the generated directories can be removed to make sure that everything is in sync.

An alternative approach, which is better in some cases, is to start with changing the DSL model and regenerate. Thereafter you manually change or move the hand written code.

Comments