mmcdole
mmcdole

Reputation: 92805

What should a "unit" be when unit testing?

On Proggit today I was reading the comment thread on a submission entitled, "Why Unit Testing Is A Waste of Time".

I'm not really concerned with premise of the article so much as I am with a comment made concerning it:

The stem of the problem is that most “units” of code in business software projects are trivial.

Change the size of the unit until it is no longer trivial? Who the hell defined the unit of code as a single function or method anyway!?

and

Well, some of the guys I worked with wanted to define a unit as single functions. It was completely stupid. My favorite definition of "unit" is: the smallest piece of the code that can be usefully tested.

Are we spending too much time just to Mock out some objects and test a trivial piece of code and not really adding anything of value?

What should a "unit" be when Unit Testing? Are function level tests too granular?

Upvotes: 35

Views: 9038

Answers (12)

mlntdrv
mlntdrv

Reputation: 298

Usually when your design is good enough, you end up with lots of small classes and making each of them a separate unit (mocking all dependencies) would be a nightmare to:

  • mock what you need to
  • refactor

It would be just too granular. You won't be able to touch anything without a test breaking. Rather, you should keep some room for refactoring (inter-class besides intra-class) and to do so you make the unit contain several classes.

On the other hand, when your design is bad, you end up with god classes of 500-600 LOC, then the class being a whole unit is fine from a test granularity point of view. The design is still not however.

Upvotes: 0

Ashwani Tiwari
Ashwani Tiwari

Reputation: 1567

As in simple word unit in the unit test is the unit behaviour/functionality of you application or system which you want to test. Your test case should test the unit behaviour not the method. Just aiming the small test in not produce a good quality test. It should make sense.

This is example from Vladimir Khorikov in his book -

A test should tell a story about the problem your code helps to solve, and this story should be cohesive and meaningful to a non-programmer.

For instance, this is an example of a cohesive story:

    When I call my dog, he comes right to me.

Now compare it to the following:

    When I call my dog, he moves his front left leg first, then the front right
    leg, his head turns, the tail start wagging...

The second story makes much less sense. What’s the purpose of all those movements? Is the dog coming to me? Or is he running away? You can’t tell. This is what your tests start to look like when you target individual classes (the dog’s legs, head, and tail) instead of the actual behavior (the dog coming to his master)

Lets take an another practical code example, An classic ATM machine example. There is a ATM class, it include following methods

  • withdraw();

  • getBalance();

  • deposit();

You will write the test case to for deposit() something like this - -makeASingleDeposite

  • makeAMultipleDeposit

and for withdraw() something like this - -makeSingleWithDraw -makeMultipleWithDraw

You have to test the behaviour of ATM class (SUT), withdraw() and deposit(). There is not need to write separate test case to test getBalance(). As to verify the withdraw and deposit behaviour in test case , you to verify balance by calling getBalance() method.

From the point of OOP, when you are test class, you are testing aggregate behaviour of class, not its individual methods.

In case of class, which not contains any state, like services or DAO's etc and you implemented business logic using Transaction Script pattern. Then your every method is behave like procedure and you can consider the method as unit. Write a test case considering functionality provided by that method.

Same in case of FP, your method/method or function is your unit.

Upvotes: 2

KolA
KolA

Reputation: 756

The question is old and there's no precise answer but I think that "anything that makes sense" or "smallest piece that can be usefully tested" can be refined a little bit.

There's an excellent article blatantly named "Unit tests aren't tests". The title is a click-bait and it is a great read on its own but I will highlight only relevant points here.

There’s this idea in physics called “emergence”, where simple systems interacting with simple rules give rise to complex systems acting by effectively different rules. For example, atoms are relatively well-understood, self-contained models. Chuck enough of them in a box and suddenly you have the entire field of solid-state physics. ... Code exhibits emergence too. Enough interacting units and your program is vastly more complex than the sum of its parts. Even if each unit is well-behaved and works according to its unit tests, the bulk of the complexity is in their integration.

Basically try to organize unit tests around "composable units" i.e. something that is less susceptible to effects of emergence when composed together - but tests still should be simple and fast enough to be called "unit tests". Effects of emergence can't be fully eliminated and some complexity will "hide" between the units anyway - however it should be relatively small amount of complexity that can be handled by integration/system tests (which is reflected in Test Pyramid structure).

Unfortunately high composability and effects of emergence can't be measured instrumentally, I can only give couple of thoughts from the top of my head:

  • heavy usage of mocks and stubs is a smell. It's very easy to employ mocks and report nearly 100% coverage without writing any useful tests.
  • pure immutable units are more composable. In pure functional programming it is common to "unit test" at very high level.
  • for a poorly designed system there's no "right" level of unit tests. It will exhibit emergence regardless of whatever units you try to test. This is why it's a common advice to invest in integration tests while refactoring some spaghetti-coded legacy system.

Upvotes: 4

Andrew Grimm
Andrew Grimm

Reputation: 81510

I suspect that "unit testing" is a misused term. Nowadays, the term isn't just used to refer to testing a small piece of code, but it's used for any test that's automated.

I tend to write my tests before writing my code, so I can't tell you when I first write it if it's going to cause the creation of a few new lines, a new method, or a new class.

So, in a word: mu.

Upvotes: 1

akappa
akappa

Reputation: 10490

Nothing is trivial, if you take into account the Murphy's Law.

Jokes apart and assuming an OO environement, I approach Unit Testing taking a class as the unit, because often the various methods modify the internal state and I want to be sure that the state between various methods is consistent.

Unfortunately, often the only way to check consistency is to invoke various method of a class to see if they fail or not.

Upvotes: 3

Paul Sonier
Paul Sonier

Reputation: 39480

A "unit" should be a single atomic unit for your definition. That is, precisely what defines a "unit" for unit testing is rather subjective; it can depend rather strongly on precisely what your architecture is, and how your application chooses to break down functional units. What I'm saying is that there is no clearly defined "unit", except for what you define. A "unit" can be a single method which has a single side effect, or it can be a related set of methods which together define a single, coherent set of functionality.

Rationality is rather important here; it's entirely possible to write unit tests for each accessor of each class that you have; however, that's clearly overkill. Similarly, it's possible but silly to define your entire application as a unit, and expect to have a single test to test it.

Generally, you want a "unit" for testing to describe one clearly defined, clearly distinct chunk of functionality. At each level of viewing your application, you should be able to see clear "atoms" of functionality (if your design is good); these can be as simple as "retrieve a record from the database" for a low-level view, and as complicated as "create, edit, and delete a user" at a high-level view. They're not mutually exclusive, either; you can easily have both as unit tests.

The point to be taken here is that the unit you want to test is the unit that makes sense. You want unit testing to validate and verify functionality; so what a unit test tests is what you define as functionality. What is a unit of functionality? That's up to your individual situation to define.

Upvotes: 9

Steve Guidi
Steve Guidi

Reputation: 20200

Change the size of the unit until it is no longer trivial? Who the hell defined the unit of code as a single function or method anyway!?

If it is too difficult to test a "unit" defined as a method, then it is likely that the method is too large or complicated for authoring a unit test.

I follow a similar methodology suggested by rwmnau, and test at the method level. In addition to creating one test per method, I create additional tests for the different code paths in each method. At times, stressing all of the code paths can become complicated. My challenge is to try to avoid writing the types of methods unless there is no better solution in terms of performance and code complexity.

I also use mocks to test contracts between components.

Upvotes: 0

Noldorin
Noldorin

Reputation: 147280

It may seem trivial to quote Wikipedia, but I think it's very succinct and accurate in this case:

A unit is the smallest testable part of an application.

This seems to agree with the comment in your question that a unit is "the smallest piece of the code that can be usefully tested". In other words, make the unit as small as you possibly can such that it still makes sense to the developer/tester by itself.

Often you will want to test parts of a project in isolation, and then test how they interact in combination. Having various tiers (levels) of unit testing is often a wise thing to do, as it helps insure that your code is working as it should on all levels, from individual functions up to entire self-contained tasks. I personally do not believe that it is wrong, or even unhelpful, to test individual functions, so long as they are doing something useful in themselves, which can often be the case.

To be quite honest, there is no definite or rigorous definition of a "unit" in "unit testing", which is precisely why the vague term "unit" is used! Learning what needs to be tested and at what level is a matter of experience, and quite often, simply trial and error. It may sound like a slightly unsatisfying answer, but I believe it is a sound rule to follow.

Upvotes: 24

Jeremy Frey
Jeremy Frey

Reputation: 2405

To me, a "unit" is any significant behavior of a class that, in 8-12 months down the road, I'm not going to remember.

And with well-written unit tests (think BDD), I won't have to, because my test will describe to me and verify the code in plain English.

Upvotes: 1

Chris Dodd
Chris Dodd

Reputation: 2960

In general, the smaller you can make your units, the more useful and effective your unit tests will be. The whole point of units tests is to be able to localize any newly introduced bug to a small part of the code.

Upvotes: 0

SqlRyan
SqlRyan

Reputation: 33914

I've always tested at the function level, and that's worked just fine. The big point for me is that unit testing tests the contract between two pieces of code. The unit test just acts as a caller, and ensures that the code being tested (no matter how large - a single function or a huge, nested library that takes 30 minutes to test) returns results in a predictable way.

The whole point of a unit test it to ensure compatibility (by not breaking intereactions) and ensure predictable results. Testing at any place there's an exchange of information will help stabilize your application.

UPDATE: I've also always added a unit test whenever a customer or user reports an edge case that breaks an interaction in my application. Fixing it isn't sufficient for me - adding a unit test to ensure that the fix holds prevents regression, and helps keep things stable down the line.

Upvotes: 5

mmr
mmr

Reputation: 14919

I'd say a unit is the smallest meaningful portion of work that can be separated from other steps in pseudocode.

For example, if you're trying to do a series of steps to process some data, like

  1. scan the dataset for the min, max, median, mean
  2. generate a histogram
  3. generate a remapping function
  4. remap the data
  5. output the data

Each of those steps would be a 'unit', and the entire series is itself also a 'unit' (to test that the cohesion between each one is working). Any one of these steps could be four functions (for the first step, if the programmer is running four loops), or one function, or whatever, but in the end, known inputs should give known outputs.

Upvotes: 4

Related Questions