Friday, October 14, 2016

Testing for Code Structure

Our students learn to use Test-Driven Development in CS1, so they are pretty good at writing tests by the time they are in our upper division courses.  This week, in my Large Scale Architecture class we hit on a different kind of testing that they hadn't seen before: testing the structure of the code.

We were studying the Lazy Load pattern and ran into this quote in the text: "The most intrusive elements of ghosts is that every accessor needs to be modified so that it will trigger a load if the object actually is a ghost." [Patterns of Enterprise Application Architecture by Martin Fowler]  That quote generated a great discussion when we asked these questions:

  • What happens if you forget that in a getter?
  • What happens if you forget that in a setter?
  • How does inheritance affect that?
In the end, we decided that this represented a pretty big risk, so I asked the students if they could write a JUnit test that, given a class, could verify that all of the getters called the load function. (I left out testing setters because they have the additional challenge of passing the right type of object into them.)

After some discussion, the students wrote a "verifier" class that uses reflection to find all of the getters in a given class that extends a Ghost class, calls each one and makes sure that each one has the side effect of calling a load method.  This has two significant questions:
  1. Should you test inherited getters?  The answer is yes, but then you also inherit the getClass() method, so you have to handle it separately
  2. How do you check if the load method is called?  This one is a bit harder.  The solution they came up with was to make a mock class that extended the class under test overriding the load method.  The overriding method would either set a boolean or increment a counter when it was called.  The JUnit test could use the verifier to call all of the getters on the mock class and verify that the load method was called correctly.
I like this lesson because it shows the students that we can use JUnit tests to test the structure of the code in addition to testing its behavior.  That allows us to put in place safety nets that catch us when we forget things like "every accessor has to call the load method." 

What a useful class period that one was!

No comments:

Post a Comment