6

I have this problem with unit testing for years. Unit testing should test isolated units, that is, if we use interface/implementation vocabulary, it should test concrete implementation classes. Fine, let's do it. In a code that is the client of the interface, one should mock the interface to suit the tests. That's OK, let us do it where possible, too.

But then, in the productions code, there is some place where the actual implementation class is instantiated and used as the-instance-that-implements the interface. It may be factory methods, it can be somewhere else.

I have the problem how to actually test this glue between those two, correctly tested in isolation, layers. Using variants of instance of is brittle - not to say, the creator of the actual instance may be "smart" and choose different implementation based on circumstances. Even if not, it creates that instance using some configuration around.

So far, I either do not unit test in strict manner (so I do not do new Foo(directa, directb), but createApp(indirectA, indirectB).getFoo() instead), but that may not be proper unit testing. That does not test all the implementations, that test those-implementations-app-uses. Other approach I may see is to make tests of Foo implementation parametric, having a factory method makeFoo(a,b) and create subclasses for each implementations as well as one which creates those instances via an actual app.

Do you know of a good way on how to approach this?

In other words, the question may be simply "How to (unit) test glue code?"

  • Can you keep all the unit tests in a separate project, so that test-only implementations can't be imported when building the real thing? – Doval Feb 25 '15 at 19:22
  • @Doval I don't understand. – herby Feb 25 '15 at 19:26
  • You said: "But then, in the productions code, there is some place where the actual implementation class is instantiated and used as the-instance-that-implements the interface. I have the problem how to actually test this glue between those two, correctly tested in isolation, layers." Rather than testing that the "actual implementation" is used, can you organize your project so you can't import any of the mocks when building the application? – Doval Feb 25 '15 at 19:32
  • I don't have the problem of "not importing the mocks". I have the problem of "test that the thing instantiated is the right one". Especially in the duck-typing world. So when there is some this.foo = new Bar(this.baz, this.quux) somewhere in the glue code, how to sanely test the this.foo is actually the good object implementing all the behaviour that is wanted, initialized properly, etc. – herby Feb 25 '15 at 19:49
  • I don't have the global understanding of that matter to write a perfect answer, so I'll forward you this talk. This guy explains the problem you have and provide the solution – Thomas Ruiz Feb 25 '15 at 19:51
8

The way you test glue code is by running an integration test.

The purpose of integration testing is to verify functional, performance, and reliability requirements placed on major design items. These "design items", i.e. assemblages (or groups of units), are exercised through their interfaces using black box testing, success and error cases being simulated via appropriate parameter and data inputs.

Simulated usage of shared data areas and inter-process communication is tested and individual subsystems are exercised through their input interface. Test cases are constructed to test whether all the components within assemblages interact correctly, for example across procedure calls or process activations, and this is done after testing individual modules, i.e. unit testing.

  • That's duplication, though. You essentially copy same tests you did in unit tests (not always, of course, but for some scenarios, when the number of combinations in sub-part is big, integration test must test them all, albeit in complicated setup). – herby Feb 25 '15 at 21:26
  • 2
    I never said it was easy. Good testing requires effort. See also programmers.stackexchange.com/a/274481 – Robert Harvey Feb 25 '15 at 21:28
  • Testing that should "drive the process" and / or "give early feedback" should not be that hard, or it loses its meaning. Testing for testing correctness, there yes. – herby Feb 25 '15 at 21:33
6

There are two ways of writing unit tests. Most people think there's only one way. If you use sociable tests, you can test the glue as part of the test.

enter image description here

  • That's what I mentioned by createApp(indirectA, indirectB).getFoo() just did not call it sociable test. That's what I basically do now. – herby Feb 25 '15 at 21:38
  • 3
    Except that depending on the other units, you're no longer unit testing. If that first box is main, you're effectively "unit testing" your whole app. Yes, I've seen people do that - and I've seen them wonder why their tests took days and were fragile, unreliable, useless things. – Telastyn Feb 25 '15 at 22:01
  • @Telastyn iaw solitary tests are unit tests and sociable tests are integration tests – jk. Feb 26 '15 at 11:37
  • 2
    @jk. you and Martin Fowler should duke it out. Because his article specifically says they're unit tests. – Daniel Kaplan Feb 26 '15 at 18:09
  • @Telastyn I believe people who write Sociable Tests still use test doubles. They just don't do it at every collaborator. – Daniel Kaplan Feb 26 '15 at 18:10
4

In an aftermath of https://vimeo.com/80533536 talk of @jbrains, posted by @ThomasRuiz, I came to this conclusion:

  • Write contract tests (the talk has strict definition of them, I have loose one: "tests that only use public API of the class").
  • Stop using new FooImpl(fooConfig) to create implementations of "instance that fulfills Foo contract", instead, make it mandatory for your code to have createFoo(fooConfig) factory method to instantiate the Foo contract worker (maybe it should be called hireFoo ;-) ).
  • Your contract tests should test against client.createFoo(testFooConfig). It may be you end up with the need of client being higherClient.createBar(...) itself. This is the weak spot of the whole thing. Maybe I should ask.jbrains.ca ... :-\

EDIT: Talked with @jbrains. The result is:

I finally know what actual dependency injection is and why it is important (your code becomes actually testable and decoupled, all the glue code is in one place and is the only untested piece).

2

But then, in the productions code, there is some place where the actual implementation class is instantiated and used as the-instance-that-implements the interface. It may be factory methods, it can be somewhere else.

How do you test that?

There are three scenarios here.

The first is that you don't care about the instance used. Some code internally uses some concrete instance to do its work and that code is already "unit" sized. That's great. Ignore the implementation detail and test the inputs/outputs.

The second is some sort of factory. For some given inputs, you get the proper output. That is an easy to test unit like any other unit - provide some inputs (tending towards boundary conditions) and make sure the output is correct. Usually instanceof is sufficient, but you could check behavior of the resultant object if it's simple/obvious/unchanging.

The third is configuration sort of code. It might be configuration itself, or it might be hardcoded inputs to more general code. In almost all of my apps for example, all main does is specify concrete instances and glue them together. I do not unit test that code, and I do not recommend you do either.

Why? Because it provides no benefit. Unit testing exists to provide early, clear signs that something is wrong. In my experience, if you screwed up your concrete instantiation (and have a relatively sane architecture) then simply running the app provides a quick, clear and cheap indicator if things are configured correctly or not.

  • Yes, I was concerned about the third case. I have some feeling towards "do not test it and make it as obvious as possible" as well. Just wasn't that sure. I would still have bad feelings a bit and will feel the need for some smoke test anyway. – herby Feb 25 '15 at 21:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.