Friday, January 25, 2008

Example Driven Development and Unit testing

In a project I once worked, we were required by company standards to write formal test specifications for manual testing. It was a lot of overhead to write these Word documents and get them through the bureaucracy. Finally, we proposed to write the test specifications as comments inside functional unit tests. That way, we could maintain the test documents easily. We saved a lot of work, and the documents got higher quality because it was easier to update them. And it really helped us to write good unit tests that covered the functionality.

In this post, I will take this approach one step further to show how unit tests and manual tests can be unified.

In an earlier post Example driven development, I argued that a few simple examples can be used for requirements, manual testing and unit testing. I don't say that a few examples are sufficient as a requirements specification, but they may be in relatively simple projects. And it is far better than nothing. Examples help you to think clearly, and to communicate accurately with others. Don't we all use examples when we try to explain something? That's the best way to explain something anyway.

The problem is that it is hard to keep the examples up to date, and then they lose the value they had for communication with the client and manual testing.

But if we implement the examples as unit tests, we completely avoid this problem! As long as the unit tests pass, the examples will be in sync with the code. And if the client changes the requirements, we modify the unit tests, and then implement the changes until the unit tests pass.

Unit tests are hard to read for non-programmers, but if we put a lot of effort into it, we can make them readable. They don't need to be writable, as programmers will write them.

Here is an example of a test of a servlet that generates licenses:
   public void testGenerateLicense() throws Exception
   {
      // Call the servlet.
      InputParams params = new InputParams();
      params.productId = "123456";
      params.quantity = 1;
      params.firstName = "Lars";
      params.lastName = "Høidahl";
      params.email = "lars@mycompany.com";
      params.company = "Object Generation";
      params.country = "Sweden";
      File licenseFile = servlet.generateLicense(params);
      
      // Check the returned file.
      assertTrue("Attachment is a license file",
            licenseFile.getName().endsWith(".lic"));
      
      // Check that 1 user was created in the database.
      List users = userDatabase.getAllUsers();
      assertEquals("Users", 1, users.size());
      User user = users.get(users.size()-1);
      assertEquals("User name", "Lars", user.getUsername());
      assertEquals("Email", "lars@mycompany.com", user.getEmail());
      assertEquals("Country", "Sweden", user.getCountry());
   }

   public void testGenerateMultipleLicenses() throws Exception
   {
      // Call the servlet.
      InputParams params = new InputParams();
      params.productId = "123456";
      params.quantity = 3;
      params.firstName = "Lars";
      params.lastName = "Høidahl";
      params.email = "lars@mycompany.com";
      params.company = "Object Generation";
      params.country = "Sweden";
      File licenseFile = servlet.generateLicense(params);
      
      // Check the returned file.
      assertTrue("Attachment is a zip file",
            licenseFile.getName().endsWith(".zip"));
      ZipFile zipFile = new ZipFile(licenseFile);
      assertEquals("Number of entries", 3, zipFile.size());
      
      // Check that 3 users were created in the database.
      List users = userDatabase.getAllUsers();
      assertEquals("Users", 3, users.size());
      User user = users.get(users.size()-1);
      assertEquals("User name", "Lars", user.getUsername());
      assertEquals("Email", "lars@mycompany.com", user.getEmail());
      assertEquals("Country", "Sweden", user.getCountry());
   }

Wednesday, January 23, 2008

Database dump with Java

I need to update a database that is created by PHP. The problem is that I am not a PHP coder, but a Java coder, and I need to use some other Java libraries to get the job done. So how can find out exactly which tables to update and how? It would take me weeks to search the PHP code, and I still wouldn't be sure if I got it right.

The first step is to install a clean application on my computer. There is no user data in the database, so if I perform commands like creating a user etc in the web application, I can look at what changed in the database. I'm sure that could be done in MySQL, but I'm not an expert on that either. When the only tool you have is a hammer, everything looks like a nail. So, I'll use Java for that to.

So, I wrote a small Java application that produces exactly the output that I need. It reads metadata from the database to find all tables and columns, lists that metadata and the content of all the rows.

Here it is:
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Properties;

public class DumpDatabase {
private Connection conn;
private PrintWriter out;

public static void main(String[] args) {
try {
DumpDatabase app = new DumpDatabase();
String timestamp = new SimpleDateFormat("MMdd-hhmmss")
.format(new Date());
String fileName = "dumpdatabase-" + timestamp + ".txt";
System.out.println("Creating " + fileName);
app.dumpDatabase(fileName);
System.out.println("Done.");
} catch(Exception e) {
e.printStackTrace();
System.exit(1);
}
}

public DumpDatabase() throws IOException,
ClassNotFoundException, SQLException {
// Get database connection from hibernate.properties.
// Or hard-code your own JDBC connection if desired.
InputStream in = getClass().getResourceAsStream(
"/hibernate.properties");
Properties properties = new Properties();
properties.load(in);
String driver = properties.getProperty(
"hibernate.connection.driver_class");
String url = properties.getProperty(
"hibernate.connection.url");
String user = properties.getProperty(
"hibernate.connection.username");
String password = properties.getProperty(
"hibernate.connection.password");

Class.forName(driver);
conn = DriverManager.getConnection(url, user, password);
}

public void dumpDatabase(String fileName)
throws FileNotFoundException, SQLException {
out = new PrintWriter(fileName);
listAll();
out.close();
conn.close();
}

public void listAll() throws SQLException {
DatabaseMetaData metadata = conn.getMetaData();
String[] types = { "TABLE" };
ResultSet rs = metadata.getTables(
null, null, null, types);
while(rs.next()) {
String tableName = rs.getString("TABLE_NAME");
listTable(tableName);
}
}

private void listTable(String tableName) throws SQLException {
PreparedStatement statement = conn.prepareStatement(
"select * from " + tableName + " a");
out.println("----" + tableName + "----");
int rowNo = 0;
ResultSet rs = statement.executeQuery();
while(rs.next()) {
if (rowNo == 0)
printTableColumns(rs);
printResultRow(rs);
rowNo++;
}
}

private void printTableColumns(ResultSet rs)
throws SQLException {
ResultSetMetaData metaData = rs.getMetaData();
for(int i = 0; i < metaData.getColumnCount(); i++) {
int col = i + 1;
out.println(metaData.getColumnName(col) + " "
+ metaData.getColumnTypeName(col) + " " + "("
+ metaData.getPrecision(col) + ")");
}
out.println("");
}

private void printResultRow(ResultSet rs) throws SQLException {
ResultSetMetaData metaData = rs.getMetaData();
for(int i = 0; i < metaData.getColumnCount(); i++) {
String column = metaData.getColumnName(i + 1);
try {
String value = rs.getString(column);
if (value != null && !value.equals("null")
&& !value.equals("") && !value.equals("0"))
out.print(column + ": " + value + ", ");
}
catch(SQLException e) {
out.print(column + ": " + e.getMessage());
}
}
out.println("");
}
}

The application reads the JDBC properties from an hibernate.properties file, for example like this:
hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
hibernate.connection.driver_class=com.mysql.jdbc.Driver
hibernate.connection.url=jdbc:mysql://localhost/users
hibernate.connection.username=<username>
hibernate.connection.password=<password>
hibernate.show_sql=true


It creates a file like dumpdatabase-01-23-10-30-38.txt:
----tb_role----
----tb_user----
user_id BIGINT (20)
username VARCHAR (255)
password VARCHAR (255)
first_name VARCHAR (255)
last_name VARCHAR (255)
email VARCHAR (255)

----tb_user_role----

Then, I create a user and run the program again to get a new dump dumpdatabase-01-23-10-32-05.txt:
----tb_role----
----tb_user----
user_id BIGINT (20)
username VARCHAR (255)
password VARCHAR (255)
first_name VARCHAR (255)
last_name VARCHAR (255)
email VARCHAR (255)

user_id: 1, username: albert, password: pw, first_name: albert,
last_name: albertson, email: albert@dot.com,
----tb_user_role----

Of course, in the real application the dumps are thousands of lines, but with Eclipse "Compare with each other", it' still easy to see what happened, so I know exactly what my Java code needs to do.

Monday, January 21, 2008

Refactor Groovy code?

I love Groovy! It's great for getting things done quickly. From Groovy, I can call Ant and XSLT easily. I can build and parse XML. I can call external applications, read file content and traverse a file hierarchy with very little code. And I can modify my program at runtime, without compiling and deploying it again.

But this dynamics come at a cost. For instance, I really miss code completion. Some times, I go to a Java editor and create an object of some class and push CTRL-SPACE to find out the parameters for a method. Also, I miss that the editor doesn't indicate errors if I misspell a function or variable name.

And I always wondered how well a dynamic language would scale. Now, I think I know the answer: It doesn't scale well! I have a Groovy template of 500 lines, and it's almost impossible to modify it. And today, I have been trying to refactor a Groovy class of 800 lines, and it's really, really hard.

How hard would it be to modify a Java class of 500-800 lines? Not hard at all. Unfortunately, the Java version of these classes would probably be several thousand lines long, so there is no perfect solution that I know of. And I can probably blame myself for hacking these unmaintainable scripts together. But that's why I loved Groovy in the first place! The ability to produce something quickly.

Actually, I write some code quickly in Java too some times, but I never have any problems refactoring it later. Maybe the problem is that there is no refactoring support in Groovy. I am so spoiled by Eclipse for Java in this area. I really hope this is the problem, and that refactoring support and auto completion comes to my editor soon. Because I love the productivity that Groovy gives.

Until then, my advice is this: Use Groovy for relatively small scripts or to replace several other tools with one. For larger programs, I think it's better to stick with Java for now. Unless you know the domain, so you write code right the first time, and never need to refactor it.

Sunday, January 20, 2008

Defensive Programming

After thinking some more about what I wrote in Small trick with Copy & Paste, I saw that that's just an example of an attitude to programming. We can call it defensive programming.

When I program, I always try to think "What can possibly go wrong here? And how can I make sure that doesn't happen?". One example is that I mess up variable names.

Another example is that copy and paste are a source of problems. If I duplicate code, and I need to change it, I may forget to change it everywhere. So, it's usually better to generalize it.

Another is that I change how a method is supposed to be used. For example, let's say I have a function:
    void sendEmail(String to, String cc,
String subject, String body)
But then I change it to
    void sendEmail(String to, String subject,
String body, String attachment)
So all callers have to change their parameters for this to work properly.

To be sure this doesn't break, it's better to rename the method:
    void sendEmailWithAttacment(String to, String subject,
String body, String attachment)
Then, the compiler will make sure it works. At least in a static language like Java. In Groovy or something you wouldn't detect this until runtime. That's why I'm not totally sold into dynamic languages, although I like Groovy a lot. If we use a dynamic language, we have to be defensive by using more unit tests.

A final example is if you fix a bug. There is always a risk that you or someone else will revert the change. How can we be sure this doesn't happen? The best way is to reproduce the bug with a unit test before fixing it. If this is too much work, at least write a comment explaining the fix.

It all comes down to admitting that I do mistakes and trying to prevent them.

Friday, January 18, 2008

Example Driven Development

This article is the first in a series about Test Driven Development. A test is an example of what a program should do. Examples make the requirements concrete, so they become easier to understand. I will show how examples can be used for requirements, manual testing and unit testing.

I am currently working on automatic order handling for a client. I will implement a servlet with the following tasks:
1. Decode input parameters from a web page.
2. Generate an encrypted license with some of those parameters.
3. Store the license in a database. I can model this database as I want.
4. Store customer data in another database. This database schema is "carved in stone".
5. Send the license as an email to the user.

This is a small project, where I will implement, test and deploy the servlet myself. If it doesn't work, my client will lose customers, and I will be responsible.

So how can I make sure this doesn't happen?

I don't trust myself to make so good quality that there won't be any problems. So, I need to do thorough testing. I will make unit tests, but how do I make good ones?

Unit testing is important, but it's not enough. There are always some issues that don't show up until manual testing. I also need to test the integration between my program and other components manually. But how do I make good manual tests? Do I need to write a formal test specification for this simple program?

I think I do. I need to discuss the requirements with my client, and some examples of what the program is supposed to do is the best way to do that. By making a few concrete examples, I can make sure there are no misunderstandings, and that no requirements are missing. I'm sure there will be some misunderstandings anyway, but that's why I'm going to test it.

I'll use this test specification both for manual testing and as basis for the unit tests. That way, I'll have good unit tests that cover the functionality well, and there hopefully shouldn't bee too many surprises when it comes to integration testing.

I start by writing down 2 examples that the servlet should handle. For each example, I will list the input parameters, output values and any changes to the databases. It doesn't take long to write these down on a piece of paper, and it will be very much worth the effort.

Example 1: A customer orders 1 license of type "Enterprise"

Input
product_id   = (ask the client what the product id is)
quantity = 1
company_name = Dot Com Inc
email = john@dotcom.com
I have already identified a very important question I need to ask my client: What are the product ids that the servlet will receive?

License Content
type         = Enterprise
company name = Dot Com Inc
email = john@dotcom.com
expires = 3/25/2009
License Database
Insert a license with the following values:
type         = Enterprise
company name = Dot Com Inc
email = john@dotcom.com
date = 3/25/2008
expires = 3/25/2009
Customer Database
(I need to ask the client about this.)

Email
to         = john@dotcom.com
subject = License
content = Please find attached an Enterprise license to
Dot Com Inc and email john@dotcom.com.
attachment = enterprise.lic
Example 2: A customer orders 1 license of type "Basic"

Input
product_id   = (Ask the client)
quantity = 1
company_name = Dot Com Inc
email = john@dotcom.com
License Content
type         = Basic
company name = Dot Com Inc
email = john@dotcom.com
expires = 3/25/2009
License Database
Insert a license with the following values:
type         = Basic
company name = Dot Com Inc
email = john@dotcom.com
date = 3/25/2008
expires = 3/25/2009
Customer Database
(Ask the client.)

Email
to           = john@dotcom.com
subject = License
content = Please find attached a Basic license to
Dot Com Inc and email john@dotcom.com.
attachment = basic.lic
Now, I need to go through these examples with my client.

When I talked to the client, the he pointed out that it is possible to order multiple licenses of the "Enterprise" product at once. I hadn't though about that. Together, we discussed how to handle this.

In the next article, I will write about how to implement and unit test the servlet.

Small Java trick with copy - paste

Here is a small trick I use to avoid getting into problems.

Some times, I need to copy a block with a variable, for instance:
      Address addr = list.get(0);
assertEquals("city", "Uppsala", addr.getCity());
and paste it to create something similar like this:
      Address addr2 = person.getAddress()
assertEquals("city", "Uppsala", addr.getCity());
Did you see the bug?

The trick I use to avoid bugs like this, is to change the variable name in the original block:
      Address addr1 = list.get(0);
assertEquals("city", "Uppsala", addr1.getCity());
Address addr2 = person.getAddress()
assertEquals("city", "Uppsala", addr.getCity());
Then the bug becomes obvious.

I can't tell you how many times this has saved me.

Saturday, January 12, 2008

Use cases and robustness analysis

Use cases or user stories? Use cases are bigger and require more work. User stories are quicker and easier, more agile. That may be ok if you have other agile practices in place. If you have a customer or functional expert available to the team at all times, user stories may work quite well. But if you don't, you may need something heavier, like use cases.

It also depends on the complexity of the requirements. If the requirements are simple, use cases are not necessary. But for complex requirements, I think use cases are better suited than user stories. They help the customer to think through the requirements earlier, and they give the developers better understanding of what the system is supposed to do.

But use cases are not agile! Yes, they may be. Agile doesn't mean lightweight, it means rightweight. It means you adapt the process to the project. A large, complex project needs a heavier process than small and simple ones.

RUP had an activity called robustness analysis to analyze model, view and controller objects from use cases. I never really understood the purpose of this, until recently I read a book called Use Case Driven Object Modeling with UML. I strongly recommend this book for everyone using use cases and/or object oriented analysis. It argues against many popular opinions about use cases that they should be abstract and not deal with the user interface. These kind of abstract use cases don't give the developers the information they need, leaving them to make their own assumptions about how the system should work.

This doesn't mean that the use cases should include user interface design, but some specification of the user interface is necessary. Not only is this necessary to keep the developers on track, it also helps to make the use cases better.

The book explains how drive the object design from the use cases, resulting in a good object model that is consistent with the requirements.

Monday, January 7, 2008

Why Agile Methods Reduce Cost

The greatest cost of software development is complexity:
  1. The problems we are trying to solve are complex. The customers often don't understand the requirements completely before the software development starts.
  2. The mapping between the requirements and the software is also complex, so that misunderstandings between the customers and the developers are the rule and not exceptions.
  3. The software itself is complex. It is hard for the developers to get an overview of what it does, and the consequences of modifying the software are hard to predict.
This complexity adds risk to software development projects, so it is actually uncommon for software projects to complete in time and on budget. Complexity also results in poor quality, since errors occur all the way in the development process; in the requirements, while mapping the requirements to the implementation, and in the implementation.

The risk and the low quality obviously increases the development cost. The cost is also increased by the the need for more developers and experts who know how to handle the complexity, and the need for substantial testing to find and fix errors.

So how can we deal with this complexity?

Requirements
We cannot reduce the complexity of the requirements, but I beleive a good domain model is important to understand complex requirements. Make the model concrete by using examples. Model the object instances for a use case instance, and go through the variations and changes that can happen with the customer.

Communication
The number one success factor in development projects is communication. We need to improve communication between customers and developers, and within the development team.

Agile methods does this through a number of key practices:
  • Iterative development gives rapid feedback on misunderstandings between customers and developers.
  • Ideally, a customer should be available to the developers all the time.
  • Daily stand-up meetings improve the communication within the team.
  • Pair programming.
Also, the domain model is very valuable as a means for communicating within the team.

Software Complexity
By complexity of software I mean how many thoughts the developer has to hold in his head at once. Early Java frameworks separated the code into lots of losely connected classes and XML files. The purpose was reuse and easy configuration, but it nevertheless made the program fragmented and hard to understand.

I personally hate it when I have to jump from a Java class to struts-config.xml to find the name of a form Java class. I just want to push F3. I find it ironic that software developers that are designing user interfaces for a living can come up with something as awkard as XML configuration files.

It is said that the human mind can hold between 5 and 9 pieces of information at once. If you need to hold the 1 action class, 1 form, 1 DAO, 1 JSP page, 2 XML files and 1 Javascript file, that doesn't leave much space for the feature you are trying to develop.

Luckily, this situation is beeing addressed by a new generation of programming languages and frameworks such as Ruby on Rails.

I beleive the cost of switching to a simpler framework will pay back several times by the improved speed that comes by being able to focus on what you are actually trying to do.