Main excuse from the developer for not having a good unit testing is "Code is not designed in a unit testable fashion." I am trying to understand what type of design and code that can't be unit tested.
Several factors may render the code difficult to unit test. When this is the case, refactoring helps in improving the code in order for it to be testable.
Some examples of code which would probably be difficult to test:
- A 1000-LOC function,
- Code which heavily relies on global state,
- Code which requires concrete, complicate to build objects, such as database context, instead of relying on interfaces and Dependency Injection,
- Code which performs slowly,
- Spaghetti code,
- Legacy code which was modified for years with no care about readability or maintainability,
- Difficult to understand code which has no comments or hints about the original intention of the author (for example code which uses variable names such as
function pGetDp_U(int i, int i2, string sText).
Note that the lack of clear architecture doesn't render code difficult to unit test, since unit tests concern small parts of the code. Unclear architecture would still have a negative impact on integration and system testing.
There are a lot of things that make code difficult to unit test. Coincidentally a lot of those also happen to make code difficult to maintain:
- Law of Demeter violations.
- Creating objects inside a method instead of injecting dependencies.
- Tight coupling.
- Poor cohesion.
- Relies heavily on side effects.
- Relies heavily on globals or singletons.
- Does not expose much intermediate results. (I once had to unit test a ten page long math function with a single output and no available intermediate results. My predecessors basically hard-coded whatever answer the code happened to give).
- Depends heavily and directly on services which are difficult to mock, like databases.
- Runtime environment is significantly different from development environment, like an embedded target.
- Units only available in compiled form (like a third-party DLL).
Common examples of code people don't wish to unit test:
- Code which directly interacts with i/o (reading files, direct network calls, …).
- Code which directly update the UI.
- Code which directly references singletons or global objects.
- Code which implicitly change the object or sub-object state.
Using a mock framework, all of these examples can be unit tested. It's just work to setup the mock replacements for the internal dependencies.
Things which truly can't be unit tested:
- Infinite loops (for a thread manager, driver, or some other type of long running code)
- Certain types of direct assembly operations (which some languages support)
- Code which requires privileged access (not impossible, just not a good idea)
The are a few areas that can make it more difficult to write unit tests for. However, I would stress that that doesn't mean you should discount useful techniques out of hand simply because they may add some complexity to your testing. As with any coding you should be doing your own analysis to determine if the benefits outway the costs, and not blindly accept what some random guy posts on the net.
Poorly written of designed code
- inappropriate coupling (usually tight coupling where it shouldn't be)
- kitchen sink code (where a function has far too much logic/responsibilities)
Reliance of state in a different scope
The cost for most of these spiral out of control unless you know what your doing. Unfortunately, many often don't know how to use these techniques in ways to mitigate things like testing complexity.
- Hardware/device dependencies
- Network dependencies
- Filesystem dependencies
- Inter-process dependencies
- Other system call dependencies
- Threading (locks, critical sections, etc)
- Call Backs
- Signal Handlers (not all, but some)
There's no such thing as code that can't be tested. There are, however, a few examples of code that's REALLY, REALLY hard to test (to the point of possibly not being worth the effort):
Hardware interactions - If the code directly manipulates the hardware (for instance, writing to a register to move a physical device), then unit testing it might be too difficult or expensive. If you use real hardware for the test, that can get pricey to get appropriate feedback into the test harness (yet more equipment!), and if you don't, you have to emulate the exact behavior of physical objects - no small trick in some instances.
Clock interactions - This is usually easier, because it's almost always possible to mock out the system clock functions pretty trivially. But when you can't, then these tests become unmanageable - tests that are based on real-time tend to take a long time to run, and in my experience they tend to be very brittle as system loads make things take longer than they should, causing phantom test failures.
My main three groups for this are:
code that relies on external services
systems that don't allow testers to modify state independently of the application.
test environments that don't replicate the production setup.
This is what I've experienced the most as a developer turned QA engineer.