Unit Testing 101: Creating Flexible Test Code
Join the DZone community and get the full member experience.
Join For Freeintroduction
when going for test-driven development , we all know that the unit test code becomes critical part of the code base. additional to this, it even ends up representing a bigger percentage of the overall code base than the actual business code. is because of this that it is really important to have a solid unit test source code structure; flexible, intuitive and maintainable enough so that when the amount of unit tests grow, it does not become an uncontrollable mess.
always remember: unit testing is not done because is a nice to have or because test-driven development is trendy and cool; it is done because is needed and is critical in order to ease the development cycle and ensure product quality.
academic v.s. real life
most articles we follow on the internet on how to build unit tests often use small or reduced scopes in order to keep the concepts easy to digest and understand. for example, they demostrate how to create one or two unit tests to test a simple application feature, like a calculator capability to add two numbers. the problem raises when we get the next day in our computers at work and try to apply our newly acquired knowledge to a more realistic scope: we realize that, in real life, we would have thousands of units tests for a thousand features and not one or two like in the articles we learned from.
so, the question is: what do we do when we go beyond examples and get into testing a real-life application?
the scenario
before we can continue, lets use the scenario we mentioned previously. lets assume we are working in an online shopping application. we are about to create a rest service that allows web clients to handle orders from customers. that service will eventually feature several methods to place new orders, cancel orders, track orders, etc. we'll need to create a bunch of unit tests for that service and all of its methods.
a flexible structure
now, we have a scenario. assuming the orders service eventually will feature a bunch of methods, unit tests must cover all of these methods and all of its possible success and failure scenarios. this could result in a couple dozen test methods in no time. what do we do? do we go down the usual path and put all of these test methods in a single test class since it is testing a single component?
short answer: no. you don't do that.
we are starting to see that grouping tests per-component is not enough in order to keep test classes small, so we need to break down grouping one more level. in the case of the orders service, our next grouping level would be per-component feature . we have now two levels of unit test grouping:
- component tests: top-level group of unit test suites that target a single component. in this case, the orders service.
- component feature tests: sub-level that group the test suites that target a single component feature. in this case, the orders service method for placing orders.
now, how does this look in code?
the unit test code
in c#, we can create a single class composed of multiple source files. this is done by using partial classes. so, we will start by defining a new partial class that will represent a part of the component test suite class.
[testfixture] partial class onordersservice { // more code here on a second... }
the component test suite class uses the following naming convention:
public [partial] class on[componentname] { }
so, when reading the name we have a clear idea of what's the target component to be tested by the suite. right after the feature test suite class declaration, we start defining the component feature test suite class. this will be a class nested inside the feature test suite class.
[testfixture] partial class onordersservice { [testfixture] public class whenplacingorders { // component feature test methods to be added here soon... } }
the component feature test suite class uses the following naming convention:
public class when[actionperformed][featurename] {}
of course, the next thing to do is add some test methods. lets expand our example:
[testfixture] partial class onordersservice { [testfixture] public class whenplacingorders { [test] public void shouldreturnorderidonvalidorderplaced() { // test code for a success scenario... } [test] public void throwerroroninvalidorderplaced() { // test code for a failure scenario... } } }
the success scenario test methods use the following convention:
public void should[expectedoutcome]on[scenariocondition]();
.. and failure scenario use:
public void should[expectedfailure]on[failureconditions]();
before you stop reading and yell in anger on those funky class names, lets try to read textually down our hierarchy:
on orders service... when placing orders... should return order id on valid order placed.
so, by now you should be getting a clearer picture on what we are trying to achieve here. by manipulating class structures (partial and nested classes) we can organize our tests by component and then by component feature while keeping an intuitive and readable structure. this will allow us to keep test classes small and easy maintainable. if you don't trust me with the previous example, this is how the visual studio test runner looks when using our handy class structure.

now, lets say that we would like to add tests to cover the service method that cancels orders. in this case we would create a new partial class using the same
onordersservice
class name. we then create a new nested class called
whencancellingorders
.
now we have a component test suite class split in two files. each file will contain a single component feature test suite.
i think is time we discuss what naming convention these files should follow. our test runner looks pretty now, but our file structure, which has nothing to do with the runner; has to look pretty as well. we will use the following convention:
on[componentname]when[actionperformed][featurename].cs
for example:
onordersservicewhenplacingorders.cs onordersservicewhencancellingorders.cs
there you go. now it looks good enough. we can go further and play a little with the namespaces, but that is out of the scope of this article.
conclusion
that would be it. we now have a consistent test code structure that can grow gradually as requirements and features are added. in future articles we will go through unit test method structuring and test contexts in order to delegate test contest setup code like mocking objects and such.
Opinions expressed by DZone contributors are their own.
Trending
-
What Is React? A Complete Guide
-
Execution Type Models in Node.js
-
Getting Started With the YugabyteDB Managed REST API
-
What ChatGPT Needs Is Context
Comments