Can I Function Test My Units?

Many testers will tell you that they eventually had to work with their development group to draw the line between unit testing and functional testing. This, of course, implies that there is some sort of a difference between the two. And, in fact, if you go by the literature — professional or otherwise — of the testing industry, there supposedly is a difference.

I’ve just never liked that fact because “functional” is such a broad term. To my way of thinking, unit testing is a form of functional testing. I think testers who use the term “functional” as a separating category within tests are stuck in an “old school” mode of thinking.

Just to give everyone the context for how I look at the world, functional testing is an approach and unit tests are one technique used in that approach. A unit test does test how something functions, after all. Another technique is to use integration tests. These, too, test how something functions. But this lets me frame the question around where I draw the line between a unit test and an integration test. That, to me, is a much more useful distinction for testers to be thinking about.

So where do I draw the line? Well, I do so on a gradient. One aspect of my gradient involves error localization. Error localization is one reason why I would write a unit test in the first place. If my test starts having multiple points of error, then I believe I have started on the path of an integration test. Another aspect of this is the need for mock objects. When I start requiring mock objects for dependencies, I consider that I have moved from a unit test into an integration test.

So, as an example, let’s say I have an accounting module in my application. Such a module will, of course, be driven by lots of calculations. The module will also be put in contact with other modules, such as an income/expense report module, a general ledger module, an asset allocation module, and so on. So here’s how I break down the testing.

  • A unit test would be at the level of the calculations in the accounting module. If I pass in some number, and a certain method takes that number and does something with it, then I expect some other number to come out — that’s my unit test. And I may have a series of unit tests for just that method that test various numbers. My unit test is solely testing the logic.
  • My integration test would then be geared towards making sure that the calculation goes from the accounting module to the income/expense module and the general ledger module. This would not require the user interface. I’d still be working with code behind the scenes, creating objects as needed.
  • A system test (yet another technique in a functional testing approach) would do the same thing as the integration test, but at the user interface level.

All of these are functional tests. They test how something functions at different levels of granularity. Which is why “functional testing” is too broad a term for me. All of these tests that I just described can also serve as acceptance tests, even though I realize many testers don’t look at it that way. The actual acceptance test will usually be at the scenario (and thus system) level. But the other tests down the chain are just ways to make sure you catch problems as early as you responsibly can. For example, during the testing of the accounting module above, the same objects are instantiated at the system test level as at the integration test level. The difference is that in the system test level I don’t instantiate the objects directly through the code, but rather through the user interface. However, a bug found at the system test level could probably most responsibly be caught at the integration test level.

As a slightly more specific example, consider the Struts framework in Java. A well-designed Struts application promotes the definition of actions that delegate processing to business components and services. Struts is a Model-View-Controller (MVC) framework. Right away this gives me a way to think about where my unit tests will go (Model) and where my integration tests will go (Controller). Specifically, I can unit test the Model (data) portion of the application. I say this because a unit test, in this context, should be run independently of the Web application and the servlet container. The only dependency for the unit test is to the class being tested.

So let’s say the application has a BookSearchService class that provides methods to search for books by author, publisher, or ISBN. Just testing my model may lead to some unit tests like these:

My next step may be to test the Controller behavior. Here the focus is on verifying the behavior of Struts actions. The Action classes are responsible only for transferring data from the View layer to the Model layer and vice versa. So I would want to check that an action performs as expected. This includes ensuring that actions:

  • Receive the data they need (from a request).
  • Call the model (that is passing the data).
  • Store the data in a context.
  • Return the correct action (the response to the request).

These types of tests can be performed in a running container (i.e., a servlet engine such as Tomcat) or outside of the container in a normal client Java Virtual Machine. So here’s a rough sketch of this idea:

Setting the request path before the test ensures that the desired action is called. As far as the test, a request parameter is added that represents the data that would be submitted from the HTML form on a search.jsp page. The call to actionPerform() actually executes the action. After that, the behavior that results from the search is verified. Specifically, the results property of the SearchForm object is checked for null, which would mean no search results came back. Here the tests will tell me that the controller is invoking the appropriate action and that it is forwarding any information to the correct presentation page. What confirms that the page is actually present and renders correctly? That’s where I test the view and I would most likely do that at the level of the browser and use a tool like Selenium or Watir to automate it.

So, going with the above example, let’s look at some possible tests at each level:

Unit Testing

Integration Testing

System Testing

You can see how I’m testing essentially the same functionality but at different levels. (You might also notice that the further I get from the code, the more extensive the tests become.) In all cases, you can hopefully see that I’m executing a functional test. Functional testing is an approach. I used three different techniques — unit, integration, and system. Each of those was designed to find bugs in the same area of functionality, but obviously at different levels and potentially at very different times. A unit test failing should presumably be noticed during any build. An integration test may likewise be found at such a time but even if not then, it will certainly be found before full system testing begins. System testing, in this case, would be the last attempt to find an error in this area of functionality.

So a few final wrap up points here:

  • You are doing a unit test when you specify and test one point of the contract of single method of an object. You should have no external dependencies. The focus is on defining the behavior of a particular class (and only that class).
  • You are doing an integration test when you require the correct inter-operation of multiple objects or multiple systems — but you do not require the user interface. You may have external dependencies. The focus is on defining the correct operation of multiple components working together.
  • You are doing a system test when you require the same thing as an integration test, but with a focus on the user interface. You will definitely have external dependencies. The focus is on defining the correct operation of the business functionality as it appears to a user.

Again: functional testing is an approach. That’s how it should be considered. You can perform this approach at various levels, utilizing different techniques. I believe testers need to think this way in order to be effective and efficient as well as being better able to coordinate their work with developers.

Share

About Jeff Nyman

Anything I put here is an approximation of the truth. You're getting a particular view of myself ... and it's the view I'm choosing to present to you. If you've never met me before in person, please realize I'm not the same in person as I am in writing. That's because I can only put part of myself down into words. If you have met me before in person then I'd ask you to consider that the view you've formed that way and the view you come to by reading what I say here may, in fact, both be true. I'd advise that you not automatically discard either viewpoint when they conflict or accept either as truth when they agree.
This entry was posted in Testing. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *