Functional Programming: Pattern Matching

Functional Programming: Pattern Matching.

Advertisements

Functional Programming: Pattern Matching

For developers coming to Scala from Java, pattern matching is a very powerful, flexible alternative to using if for conditional logic. In practice it allows for very clean, terse blocks of conditional code.
Let us take an example:

Java

  if (response.isSuccessful()) {
    if (user.isBasketEmpty()) {
      forwardUser("/welcome");
    } else {
      forwardUser("/basket");
    }
  } else {
    forwardUser("/failedLogin");
  }

Scala

response.isSuccessful match {
  case true if (user.isBasketEmpty) => forwardUser("/welcome")
  case true  => forwardUser("/basket")
  case false  => forwardUser("/failedLogin")
}

So what is happening with the Scala version? We are matching on the result of calling isSuccessful. We do not need brackets to call this method so it is ‘isSuccessful’ not ‘isSuccessful()’.

First case: the value of this is matched against true. If it is true we provide a caveat, we also want to check user.isBasketEmpty is true. If both criteria are met we will forward the user to ‘/welcome’.

Second case: This is just checking response.isSuccessful but if the user.isBasketEmpty returns false.

Third case: This is the more explicit way of catching when response.isSuccessful is false, an alternative would be

case _ => forwardUser("/failedLogin")

Where the underscore acts as a wildcard, in this case being a catch-all.

Functional Programming: Higher-order functions

Functional Programming: Higher-order functions

Higher-order functions have functions as parameters or as return types. In Java before v8 every value is a primitive type (e.g. boolean, char, int etc.) or object (String, List, etc.). In Scala a value can be a function, meaning it has properties and can be passed to and returned from other functions.

In practice this means that, where a class might be generated to ‘chaperone’ a function in java, just the method can be passed.

I will give an example:

Here is the Java version, note use of the UnitOfWork interface to chaperone the method doWork().

class TransactionManager {
  public void doInTransaction(final UnitOfWork uow) {
    startTransaction();
    uow.doWork();
    endTransaction();
  }
}

interface UnitOfWork {
  void doWork();
}

class SaveCustomer implements UnitOfWork {
  @Override
  public void doWork() {
    System.out.println("Saving a customer");
  }
}

class Job {
  public void saveCustomerInTransaction() {
    final UnitOfWork u = new SaveCustomer();
    new TransactionManager().doInTransaction(u);
  }
}

Here is the Scala version, we just  assign the function to a value and pass it:

class TransactionManager {
  def doInTransaction(doWork: Unit => Unit) {
    startTransaction();
    doWork();
    endTransaction();
  }
}


class Job {
  // Assign a function to a val(ue).
  val saveCustomerFunction = (u: Unit) => { println("Saving a customer") }

  def saveCustomerInTransaction {
    new TransactionManager().doInTransaction(saveCustomerFunction)
  }
}

Scala can be terser than the example above but I have tried to make it easier for a Java developer to read.

Layers of abstraction in Unit Tests: Have an Implementation layer

Summary

Differing levels of abstraction can aid or hinder the task of maintaining unit tests.

Disclaimer

This idea is still a work in progress, the product of working on one set of tests on one project but even if this just helps people look at JUnit tests from a fresh angle my time writing this post will have been worthwhile (I tell myself).

Problem

JUnit tests in Java are normal classes which either extend TestCase (JUnit 3) or are annotated as tests (JUnit 4). As they are Java classes, this allows us to use inheritance and to abstract out some or all of the details. Too little abstraction in our test cases can result in a test case where it is almost impossible to see what is being set up and what is being verified, there are lots of lines of code and these will typically need to be commented to show which block starts where. Too much abstraction can lead to the same problem but due to a lack of structure in the class at hand.

In a nutshell my requirements story is this:

As a developer maintaining Unit Tests, I want to see in code the prerequisites, the method invocation and verifications, with a layer of abstraction that hides low-level concerns but contains enough of the domain to see what is happening.

Too much detail:

public class LoginTest {

  @Test
  public void testLogin() {
    // Set up login request, mock authenticator and * inject into the LoginService.
    final LoginRequest loginRequest = new LoginRequest();
    final String username = “testuser”;
    final String password = “testpass”;
    loginRequest.setUsername(username);
    loginRequest.setPassword(password);
    MockAuthenticator mockAuthenticator = new MockAuthenticator();
    mockAuthenticator.setValidCredentials(username, password);
    loginService.setAuthenticator(mockAuthenticator);

    // Call login method being tested:
    final LoginResult result = loginService.login(loginRequest);

    // Verify the result 
    Assert.assertTrue(result.isSuccessful);
    }
}

Not enough detail:

public class LoginTest extracts AbstractSuccessfulLoginServiceTest {
  // Sadly, there are test classes like this and they will fail
  protected LoginRequest createRequest() {
    return new SuccessfulLoginRequest();
  }
}

Solution

Make the test class copy the story or script and push everything else into an implementation layer. This is akin to the Fixture classes sometimes used, the more this is a complete layer supporting the TestCase classes the better, it is very easy for supporting classes to become disjointed and hard to implement a coherent strategy across the board.


public class LoginTest {
  @Test
  public void testLogin() {
    // Make the login service use a mock authenticator which generates a valid username/password for us.
    MockAuthenticator mock = new MockAuthenticator();
    final LoginRequest request = mock.createGoodRequest();
    loginService.setAuthenticator(mock);

    // Call login method being tested:
    final LoginResult result = loginService.login(request);

    // Verify the result
    Assert.assertTrue(result.isSuccessful);
    }
}

Notes

If our unit test classes are just normal classes and we are trying to write good code – remove duplication, etc – why are we not abstracting out to the nth degree? We/I would do this with non-test code. The difference is that each unit test is an interface to the system. Where we run a system/application as its own collection of components, with a unit test each test is a discrete entity with its own functional requirements. At least sometimes, we can’t/shouldn’t allow too much interdependence between these classes. They have to be able to stand on their own. The implementation of what they wish to be done, creating datasets, setting up mocks etc, this can be abstracted out but a top level dictating what it is to abstract out, that should remain.

DSLs

Test classes are a great place to use a Domain Specific Language. Where a number of tests rely on a rich layer to setup or verify tests this can be successfully aided by a DSL-like structure, with lines like the following:


Verify.that(loginResult).isSuccessful();
Verify.that(outputFile).contains(domainModels);

We are now working with the problem domain in a language specific to the problem. For more information on DSLs please see
this article.

Abstraction in OOP

Abstraction is one of the four pillars of object oriented programming, but what is it and why is it important?

My take on abstraction is born of experience. Abstraction for me is about hiding detail, removing a need to know about implementation details. This is typically the kind of base explanation I give of abstraction, but the value of it comes in fact from its decoupling. We can provide a layer of abstraction between an object/class and its dependencies, vital if we want to run unit tests and not touch real resources, real filesystems, real databases etc. To benefit from this layer of abstraction we need the ability for the code to handle different types commonly, based on a common base class or interface, i.e. subtype polymorphism.

For what it’s worth, abstraction and polymorphism are pretty tough subjects when you dig deeper but for my purposes as a J2EE developer, abstraction is mainly about allowing tests to run without the need for a real database, real EJB or maybe just without creating a very complex data type which is unrelated to the test at hand.

Here’s a Java tip though, static methods will hinder any attempts at abstraction. Learn where you really need abstraction, typically where you need to avoid touching a resource or certain class in a test, then avoid static methods like the plague. Static methods cannot be overridden, therefore cannot be ‘abstracted out’. I will address this subject in another post…

Any comments/questions welcome.

Test Driven Development

Test Driven Development typically means when the test code is written first. This can be a lot of fun but doesn’t fit every scenario – when the code you are writing may not be part of the finished product, time spent writing tests can be a gamble.

A perfect fit for Test Driven Development is when writing functions with a few different paths through the code: there are a certain number of key paths through the code and these can be written out as tests, ready to be run with each modification to the code.

Let’s see how an example might work:

Imagine an online store. We have a need for a method to choose where a user will be redirected based on his login attempt but the logic is complex. If his login is unsuccessful he will go to ‘bad-login’, for successful logins he will go to ‘good-login’ unless he has items in his basket. If this is the case he will go to ‘basket’… unless he has 1 or more notifications, in which case he will go to ‘notifications.’ The method will take a UserDto and return a String.

We can write these requirements as tests and make sure that, however complex, we can cover our key scenarios and run through them in less than a second by running our test class. In this example I’ll use Scala to help anyone looking to see the direction in which Java is heading. I’ll use JUnit for the tests as Scala-Java integration is fantastic and lets a Java developer keep a lot of their favourite Java frameworks while moving to Scala.

package vff.blog
import junit.framework.TestCase
import org.junit.Assert
case class UserDto(username: String, hasNotifications: Boolean, hasItemsInBasket: Boolean) {
  // line above declares the class and constructor together - each will be immutable.
}

object RedirectService {
  // object as opposed to class creates a singleton
  val LOGIN_UNSUCCESSFUL = "bad-login"
  val LOGIN_SUCCESSFUL = "good-login"
  val SHOW_BASKET = "basket"
  def redirect(userDto: UserDto, loginSuccessful: Boolean): String = {
     "" // return an empty string
  }
}

This is my basic UserDto class holding a String and 2 Booleans and my RedirectService, a singleton (note use of the word object, not class) with a function taking a UserDto and a Boolean and returning a String. For now it returns an empty String – just enough to compile and run. For our first test we will create a ‘happy path’ test – I’d say the simplest path (not necessarily ‘happy’) is login unsuccessful (nothing else matters, return a string) so we’ll code that first.

package vff.blog

import org.junit.Assert
import junit.framework.TestCase
class RedirectServiceTest extends TestCase {
  def testLoginUnsuccessful {
    val userDto = new UserDto("vincefleetwood", false, false)
    val result = RedirectService.redirect(userDto, false)
    Assert.assertEquals(RedirectService.LOGIN_UNSUCCESSFUL, result)
  }
}

When we run this test we get a failure (as expected). Time to write some code to make our test pass. We change our method as shown:

def redirect(userDto: UserDto, loginSuccessful: Boolean): String = {
  LOGIN_UNSUCCESSFUL
}

Now our method always returns ‘bad-login’. Well, it’s a start 🙂 We can now run our test and it will pass. This might not feel like a big step but we’ll get there. Time for a second test, that when the user logs in without notifications or basket items they go to ‘good-login’:

Lets add the test first:

def</strong> testLoginSuccessfulBasic {
    <strong>val</strong> userDto = new UserDto("vincefleetwood", false, false)
    <strong>val</strong> result = RedirectService.redirect(userDto, true)
    Assert.assertEquals(RedirectService.LOGIN_SUCCESSFUL, result)
  }

We add the new constant to the Redirect Service (match is like a switch that can run on any value) and our test runs but fails.

We now conditionally return the new value:

  def redirect(userDto: UserDto, loginSuccessful: Boolean): String = {
    loginSuccessful  match {
      case true => LOGIN_SUCCESSFUL
      case false => LOGIN_UNSUCCESSFUL
    }
  }

We now write the test for items in the basket:

def testLoginSuccessfulItemsInBasket {
    val userDto = new UserDto("vincefleetwood", false, hasItemsInBasket = true)
    val result = RedirectService.redirect(userDto, true)
    Assert.assertEquals(RedirectService.SHOW_BASKET, result)
}

We also write the code to pass the test – refactoring a little what we have already written but with tests to make sure it still works:

object RedirectService { // object as opposed to class creates a singleton
  val LOGIN_UNSUCCESSFUL = "bad-login"
  val LOGIN_SUCCESSFUL = "good-login"
  val SHOW_BASKET = "basket"
  def redirect(userDto: UserDto, loginSuccessful: Boolean): String = {
    loginSuccessful match {
      case true if (userDto.hasItemsInBasket) => SHOW_BASKET
      case true                               => LOGIN_SUCCESSFUL
      case false                              => LOGIN_UNSUCCESSFUL
    }
  }
}

I’m guessing this is enough for now and leave the final test as an exercise for the reader. Please leave any comments/questions.

Unit testing – why? Part 2: What did I just break?

Ok, so I had to modify an existing class for a new feature. Does everything else still work ok?

If I have unit tests in place I can fix a lot of bugs without deploying, not just bugs in my new method but bugs in existing methods. If old code relies on a piece of code and it changes, this can break the old code. The worst example of this is fixing existing code when other code relied on it not working properly.

Having unit tests in place helps a developer see side effects of modifying existing code without the need to build, deploy and test.