74

When doing unit tests the "proper" way, i.e. stubbing every public call and return preset values or mocks, I feel like I'm not actually testing anything. I'm literally looking at my code and creating examples based on the flow of logic through my public methods. And every time the implementation changes, I have to go and change those tests, again, not really feeling that I'm accomplishing anything useful (be it mid- or long-term). I also do integration tests (including non-happy-paths) and I don't really mind the increased testing times. With those, I feel like I'm actually testing for regressions, because they have caught multiple, while all that unit tests do is show me that the implementation of my public method changed, which I already know.

Unit testing is a vast topic, and I feel like I'm the one not understanding something here. What's the decisive advantage of unit testing vs integration testing (excluding the time overhead)?

enthrops
  • 843
  • 1
  • 7
  • 6
  • 4
    My two cents: Don't overuse mocks (http://googletesting.blogspot.com/2013/05/testing-on-toilet-dont-overuse-mocks.html – Ruan Mendes Jul 11 '14 at 15:33
  • 1
    See also ["Mocking is a Code Smell"](https://medium.com/javascript-scene/why-i-use-tape-instead-of-mocha-so-should-you-6aa105d8eaf4#.6lu3bet5f) – user949300 Nov 30 '16 at 17:06

6 Answers6

46

When doing unit tests the "proper" way, i.e. stubbing every public call and return preset values or mocks, I feel like I'm not actually testing anything. I'm literally looking at my code and creating examples based on the flow of logic through my public methods.

This sounds like the method you are testing needs several other class instances (which you have to mock), and calls several methods on its own.

This type of code is indeed difficult to unit-test, for the reasons you outline.

What I have found helpful is to split up such classes into:

  1. Classes with the actual "business logic". These use few or no calls to other classes and are easy to test (value(s) in - value out).
  2. Classes that interface with external systems (files, database, etc.). These wrap the external system and provide a convenient interface for your needs.
  3. Classes that "tie everything together"

Then the classes from 1. are easy to unit-test, because they just accept values and return a result. In more complex cases, these classes may need to perform calls on their own, but they will only call classes from 2. (and not directly call e.g. a database function), and the classes from 2. are easy to mock (because they only expose the parts of the wrapped system that you need).

The classes from 2. and 3. cannot usually be meaningfully unit-tested (because they don't do anything useful on their own, they are just "glue" code). OTOH, these classes tend to be relatively simple (and few), so they should be adequately covered by integration tests.


An example

One class

Say you have a class which retrieves a price from a database, applies some discounts and then updates the database.

If you have this all in one class, you'll need to call DB functions, which are hard to mock. In pseudocode:

1 select price from database
2 perform price calculation, possibly fetching parameters from database
3 update price in database

All three steps will need DB access, so a lot of (complex) mocking, which is likely to break if the code or the DB structure changes.

Split up

You split into three classes: PriceCalculation, PriceRepository, App.

PriceCalculation only does the actual calculation, and gets provided the values it needs. App ties everything together:

App:
fetch price data from PriceRepository
call PriceCalculation with input values
call PriceRepository to update prices

That way:

  • PriceCalculation encapsulates the "business logic". It's easy to test because it does not call anything on its own.
  • PriceRepository can be pseudo-unit-tested by setting up a mock database and testing the read and update calls. It has little logic, hence few codepaths, so you do not need too many of these tests.
  • App cannot be meaningfully unit-tested, because it is glue-code. However, it too is very simple, so integration testing should be enough. If later App gets too complex, you break out more "business-logic" classes.

Finally, it may turn out PriceCalculation must do its own database calls. For example because only PriceCalculation knows which data its needs, so it cannot be fetched in advance by App. Then you can pass it an instance of PriceRepository (or some other repository class), custom-tailored to PriceCalculation's needs. This class will then need to be mocked, but this will be simple, because PriceRepository's interface is simple, e.g. PriceRepository.getPrice(articleNo, contractType). Most importantly, PriceRepository's interface isolates PriceCalculation from the database, so changes to the DB schema or data organisation are unlikely to change its interface, and hence to break the mocks.


Note: I recently noticed that this concept is fairly similar to Alistair Cockburn's Hexagonal architecture. So I guess I've just been reinventing the wheel...or maybe great minds think alike?

sleske
  • 10,095
  • 3
  • 29
  • 44
  • 6
    I thought I was alone not seeing the point in unit testing everything, thanks – enthrops May 22 '13 at 11:27
  • 5
    I just disagree when you say classes of type 3 are few, I feel like most of my code is of type 3 and there is almost no business logic. This is what I mean: http://stackoverflow.com/questions/38496185/how-to-use-functional-programming-on-app-development – Rodrigo Ruiz Jul 28 '16 at 17:43
  • I really have to ask it here. Imagine a call to the method of the mocked object returns std::string/wrapper around std::string as a result. Would you still mock a class of the result? In this case, either std::string or its wrapper. – user1415536 Sep 14 '20 at 09:14
  • @user1415536: No, you do not have to ask here, so please don't. Comments are not for new questions. You raise an interesting question, please ask it as a separate question (feel free to link this answer). – sleske Sep 14 '20 at 10:16
28

What's the decisive advantage of unit testing vs integration testing?

That's a false dichotomy.

Unit testing and integration testing serve two similar, but different purposes. The purpose of unit testing is to make sure your methods work. In practical terms, the unit tests make sure that the code fulfills the contract outlined by the unit tests. This is evident in the way that unit tests are designed: they specifically state what the code is supposed to do, and assert that the code does that.

Integration tests are different. Integration tests exercise the interaction between software components. You can have software components that pass all of their tests and still fail integration tests because they don't interact properly.

However, if there is a decisive advantage to unit tests, it is this: unit tests are much easier to set up, and require far less time and effort than integration tests. When used properly, unit tests encourage the development of "testable" code, which means the final result is going to be more reliable, easier to understand, and easier to maintain. Testable code has certain characteristics, like a coherent API, repeatable behavior, and it returns results that are easy to assert.

Integration tests are more difficult and more expensive, because you often need elaborate mocking, complex setup, and difficult assertions. At the highest level of system integration, imagine trying to simulate human interaction in a UI. Entire software systems are devoted to that sort of automation. And it is automation that we're after; human testing is not repeatable, and doesn't scale like automated testing does.

Finally, integration testing makes no guarantees about code coverage. How many combinations of code loops, conditions and branches are you testing with your integration tests? Do you really know? There are tools that you can use with unit tests and methods under test that will tell you how much code coverage you have, and what the cyclomatic complexity of your code is. But they only really work well at the method level, where unit tests live.


If your tests are changing every time you refactor, that's a different problem. Unit tests are supposed to be about documenting what your software does, proving that it does that, and then proving that it does that again when you refactor the underlying implementation. If your API changes, or you need your methods to change in accordance with a change in the system design, that's what is supposed to happen. If it's happening a lot, consider writing your tests first, before you write code. This will force you to think about the overall architecture, and allow you to write code with the API already established.

If you're spending a lot of time writing unit tests for trivial code like

public string SomeProperty { get; set; }

then you should reexamine your approach. Unit testing is supposed to test behavior, and there is no behavior in the line of code above. You have, however created a dependency in your code somewhere, since that property is almost certainly going to be referred to elsewhere in your code. Instead of doing that, consider writing methods that accept the needed property as a parameter:

public string SomeMethod(string someProperty);

Now your method doesn't have any dependencies on something outside of itself, and it is now more testable, since it is completely self-contained. Granted, you won't always be able to do this, but it does move your code in the direction of being more testable, and this time you're writing a unit test for actual behavior.

Robert Harvey
  • 198,589
  • 55
  • 464
  • 673
  • 2
    I am aware that unit testing and integration testing server different goals, however, I still don't get how unit tests are useful if you stub and mock all public calls that unit tests make. I would understand 'code fulfills the contract outlined by the unit tests', if it wasn't for stubs and mocks; my unit tests are literally reflections of logic inside of methods that I'm testing. You (I) aren't really testing anything, just looking at your code, and 'converting' it to tests. Regarding difficulty automating and code coverage, I'm currently doing Rails, and these both are well taken care of. – enthrops May 17 '13 at 03:29
  • 2
    If your tests are just reflections of the method logic, you're doing it wrong. Your unit tests should essentially hand the method a value, accept a return value, and make an assertion about what that return value should be. No logic is required to do that. – Robert Harvey May 17 '13 at 03:35
  • 2
    Makes sense, but still have to stub all the other public calls (db, some 'globals', like current user status, etc) and end up with testing code following method logic. – enthrops May 17 '13 at 03:38
  • db is integration testing. Globals hinder unit testing; if you need globals, set up a mock object or a context object, and pass that to the method. But it's better to not rely on global state at all. – Robert Harvey May 17 '13 at 03:49
  • Exactly, db is integration testing, and rails apps make lots of calls to ORMs all over the place. Also, HTTP is stateless, which means you can't not rely on globals. I'm not sure about other technologies (not much experience), but for Rails unit testing turns into a mess of mocks and stubs – enthrops May 17 '13 at 03:51
  • Well, you don't unit test ROR or ORM code; you only unit test your code. Assume that the frameworks you're using already work. – Robert Harvey May 17 '13 at 04:05
  • Well, my code calls orm and rails stuff, and their return values determine what the side effects / return values of my code are – enthrops May 17 '13 at 04:08
  • That's an integration test, not a unit test. You need to start thinking more in terms of self-containe code units. Once you unit test your own self-contained method, and prove that it works, you can always plug it into an existing framework like ROR. – Robert Harvey May 17 '13 at 04:23
  • 1
    So I guess unit tests are for mostly isolated stuff that kind of confirms to 'set of inputs -> set of expected results'? – enthrops May 17 '13 at 04:33
  • Yes, that's right. – Robert Harvey May 17 '13 at 04:36
  • 2
    My experience in creating lots of unit and integration tests (not to mention the advanced mocking, integration testing, and code coverage tools used by those tests) contradicts most of your claims here: 1) "The purpose of unit testing is to make sure your code does what it is supposed": the same applies to integration testing (even more so); 2) "unit tests are much easier to set up": no, they aren't (quite often, it's the integration tests that are easier); 3) "When used properly, unit tests encourage the development of "testable" code": same with integration tests; (continues) – Rogério Feb 22 '15 at 23:52
  • 1
    (continued) 4) "Integration tests ... often need elaborate mocking": surely it's *unit* tests that normally require mocking (most integration tests require none); 5) "Integration tests ... often need complex setup": not really, if you have appropriate testing infrastructure in place; 6) "Integration tests ... often need difficult assertions": same as 5, as you can have reusable assertion helpers for checking data in DB, etc.; (continues) – Rogério Feb 22 '15 at 23:59
  • (continued) 7) "At the highest level of system integration, imagine trying to simulate human interaction in a UI": difficult, yes, but can only be achieved with integration tests (using tools like Selenium), not with unit tests; 8) "integration testing makes no guarantees about code coverage": non-sense, code coverage tools can be used just as well with integration testing (I should know, as I do it on a daily basis); 8) "If your tests are changing every time you refactor, that's a different problem": yes, but it's a much smaller problem with integration tests, as they are a lot less fragile. – Rogério Feb 23 '15 at 00:07
  • 2
    @Rogério: Perhaps you should post your own answer. – Robert Harvey Feb 23 '15 at 00:08
4

The unit tests with mocks are to make sure the implementation of the class is correct. You mock the public interfaces of the dependencies of the code that you are testing. This way you have control over everything external to the class and are sure that a failing test is due to something internal to the class and not in one of the other objects.

You are also testing the behavior of the class under test not the implementation. If you refactor the code (creating new internal methods, etc) the unit tests should not fail. But if you are changing what the public method does then absolutely the tests should fail because you have changed the behavior.

It also sounds like you are writing the tests after you have written the code, try instead writing the tests firsts. Try outlining the behavior that the class should have and then write the minimum amount of code to make the tests pass.

Both unit testing and integration testing are useful for ensuring the quality of your code. The unit tests examine each component in isolation. And the integration tests make sure that all the components interact properly. I want to have both types in my test suite.

Unit tests have helped me in my development as I can focus on one piece of the application at a time. Mocking the components that I haven't made yet. They also a great for regression, as they document any bugs in the logic that I have found (even in the unit tests).

UPDATE

Creating a test that only makes sure that methods are called has value in that you are making sure that the methods actually do get called. Particularly if you are writing your tests first, you have a check list of methods that need to happen. Since this code is pretty much procedural, you don't have much to check other than that the methods get called. You are protecting the code for change in the future. When you need to call one method before the other. Or that a method always gets called even if the initial method throws an exception.

The test for this method may never change or may change only when you are changing the methods. Why is this a bad thing? It helps reinforce using the tests. If you have to fix a test after changing the code, you will get in the habit of changing the tests with the code.

Schleis
  • 3,376
  • 1
  • 19
  • 21
  • And if a method doesn't call anything private, then there's no point in unit testing it, correct? – enthrops May 17 '13 at 01:49
  • If the method is private, you don't test it explicitly, it should be tested via the public interface. All public methods should be tested, to ensure that the behavior is correct. – Schleis May 17 '13 at 12:45
  • No, I mean if a public method does not call anything private, does it make sense to test that public method? – enthrops May 17 '13 at 17:19
  • Yes. The method does something doesn't it? So it should be tested. From a testing stand point I don't know if it is using anything private. I just know that if I provide input A, I should get output B. – Schleis May 17 '13 at 17:23
  • Oh yes, the method does something and that something is calls to other public methods (and only that). So the way you would 'properly' test that is stub the calls with some return values and then set up message expectations. What EXACTLY are you testing in this case? That the right calls are made? Well, you wrote that method and you can look at it and see exactly what it does. I think unit testing is more appropriate for isolated methods that are supposed to be used like 'input -> output', so you can set up a bunch of examples and then do regression testing when you refactor. – enthrops May 17 '13 at 18:02
  • In the method that you are talking about, I am making sure that the proper calls are being made. The method's job is to call objectA.foo() then objectB.bar() so that is what I am going to make sure. Suppose at some future date, the order matters. You are able to adjust the test to ensure this. Or that conditionally objectC.baz() will be called, the test will ensure that. – Schleis May 17 '13 at 18:52
  • The whole point of tests is to prevent regressions, right? If you add a call to objectC.baz(), your tests are going to break and the way you would fix that is to rewrite the test to mirror the changes in the method. The question stands, how is that going to test for regressions? Now imagine a method that calls many private methods to do some complex calculations. The way to test it would be to provide a set of inputs and expected outcomes, so when you refactor / change algorithm, you will be able to catch regressions. This is where unit testing makes sense, not the situation you're describing. – enthrops May 17 '13 at 19:20
3

I was experiencing a similar question - until I discovered the power of component tests. In short, they are the same as unit tests except that you don't mock by default but use real objects (ideally via dependency injection).

That way, you can quickly create robust tests with a good code coverage. No need for updating your mocks all the time. It might be a bit less precise than unit tests with 100% mocks, but the time and money you save compensate for that. The only thing you really need to use mocks or fixtures for are storage backends or external services.

Actually, excessive mocking is an anti-pattern: TDD Anti-Patterns and Mocks are evil.

lastzero
  • 101
  • 1
  • 2
0

Though the op has already marked an answer, I am just adding my 2 cents here.

What's the decisive advantage of unit testing vs integration testing (excluding the time overhead)?

And also in response to

When doing unit tests the "proper" way, i.e. stubbing every public call and return preset values or mocks, I feel like I'm not actually testing anything.

There is a helpful but not exactly what OP has asked:

Unit Tests work but there are still bugs?

from my little experience on testing suites, I understand that Unit Tests are always to test the most basic method level functionality of a class. In my opinion every method either public, private or internal deserves to have a dedicated unit tests. Even in my recent experience I had a public method that was calling other small private method. So, there were two approaches:

  1. don't create a unit test(s) for private method.
  2. create a Unit Test(s) for a private method.

If You think logically, the point of having a private method is: main public method is getting too large or messy. In order to solve this You refactor wisely and create out small chunks of code that deserve to be separate private methods which in turn make Your main public method less bulky. You refactor by keeping in mind that this private method might be reused later on. There can be cases in which there are no other public method depending upon that private method, but who knows about future.


Considering the case when private method is reused by many other public methods.

So, If I had chosen approach 1: I would have duplicated Unit tests and they would have been complicated, as You had number of Unit Tests for each branch of public method as well as private method.

If I had chosen approach 2: the code written for unit tests would be relatively less, and it would be much easier to test.


Considering the case when private method is not reused There is no point of writing a seperate unit test for that method.

As for as Integration Tests are concerned, they tend to be exhaustive and more of high level. They will tell You that given a input, all of Your classes should come to this final conclusion.To understand more about usefulness of integration testing please see the mentioned link.

Devesh
  • 427
  • 1
  • 5
  • 16
-1

I have moved away from unit testing, and almost exclusively prefer integration testing.

Actually, I dislike the terms "unit" and "integration". I would prefer to say that I focus on testing the public interface, and only the public interface.

Sometimes the public interface is a fairly low-level API. In which case, you write tests that directly test that API. These are likely to be fairly low-level tests and could fairly be called "unit tests".

But more often, the public interface is at a higher level, and your units are an implementation detail of that public interface. There are several problems with focusing on the units and exhaustive unit testing instead of higher-level tests (which I have learned by painful experience):

  1. Being part of the implementation, units are subject to change. Often, you will not discover this until you try and integrate the units together, and you realise that you have to re-write some or all of the units. This pain is unavoidable and part of the software development cycle, but if you've dutifully and thoroughly written unit tests for all of said units, the pain is doubled. In contrast, if you've written your tests at the level of public interface ("integration" level), these tests will often remain the same through a major refactor of your units. They will give you confidence that your overall system is still doing what it was meant to even as you refactor the units.
  2. As a variant of the first point, if you focus on the unit level and thorough unit testing, it's easy to get carried away and find that you've written code in your units that wasn't really necessary when you started to integrate them (again doubling the waste if you've written tests for the redundant code). If you apply test-driven practices at the level of public interface, you tend to avoid falling into the trap of writing unnecessary code in your units and you'll only write what is necessary.
  3. Because units interact with other units when they are integrated, you end up needing to mock those other units to isolate your unit-under-test to get a "pure" unit test. You've clearly experienced the drawbacks of mocks - they are by definition always less accurate than the units they are mocking, which leaves sleeping bugs in your units that don't show up until you start to integrate them. Attempts to fix this over time produce mocks that are often nearly as complicated as the unit they are mocking (but still never as accurate).

It is true that integration tests often run slower than unit tests. But IMO it doesn't matter how fast the tests run if they are inaccurate or testing code that is redundant. I prefer the accurately test code that is actually necessary to achieve what I want to achieve (the "public interface").

It is also true that you sometimes can't get 100% coverage of your internal units with integration tests alone. But in my experience, this is usually an indication that you have functionality in your units that you don't really need.

At the end of the day, we care about what our software does - not how it does it. We should test what we care about, and avoid testing what we ultimately do not care about as that is a waste of time and effort. Units are generally more about the "how" than the "what".