DEV Community

Lars Richter
Lars Richter

Posted on

Is testability a reason to change your design?

The following discussion is something I experience on a regular basis:

Person A: Why do you extract an interface for this? Can't you just make a normal class for this?
Person B: Of course I could just make a class. But it will be hard to test, because I cannot replace the implementation easily.
Persion A: So you're changing your design just for testability? Why would one do it? It still would be possible to test it, if you implement it as a simple file. Maybe it's simpler with the abstraction, but you are writing more code.
Person B: Isn't testability a good enough reason to make that design choice?
Person A: I don't know? I think I would not do it. It's just testability, you know?

Almost every time I'm "Person B". For me, testability of a feature/application is a big win. And if I can improve testability by adding a level of abstraction, that's a good thing.

Of course there are times when testability isn't that important. For "toy projects" or just small pieces of code it might not be relevant. But in most cases I'm working on complex projects with a big amount of legacy code. While working on those projects, in my opinion, testability is pretty valuable.

I would love to hear your opinion on that topic. Would you change your design just to achieve/improve testability?

Latest comments (33)

Collapse
 
shiling profile image
Shi Ling • Edited

Don't test for the sake of 100% code coverage.

Not everything is worth the time or effort to test.

For example, IMHO, CRUD operations aren't quite worth the effort, because they are straightforward enough that fellow engineer can easily spot a mistake during a code review, and also because they will be used often enough in various parts of the application that mistakes would be obvious and emerge very quickly.

I automate tests (and refactor for testability) when the logic is complex enough that mistakes would be difficult to spot during code reviews, for example where there are computations and decision trees.

Collapse
 
jvanbruegge profile image
Jan van Brügge

For me it's 100% yes for testability. But not for actually writing tests, that's just a nice bonus. Ask yourself: Why does this make the code more testable? Most of the time it is: Because i can inject/mock side effects, e.g. a database call. This means your design change divides the logic from the side effects and testability is just a result of this.
I am one of the maintainers of Cycle.js and we design our code to be testable and visualizable. This naturally leads to clearly seperated side effects from app logic, with the app logic being a pure function. As we all know, pure functions are way easier to test than side effectful functions, so our architecture results in testable code.

Collapse
 
n_develop profile image
Lars Richter

Why does this make the code more testable? Most of the time it is: Because i can inject/mock side effects, e.g. a database call. This means your design change divides the logic from the side effects and testability is just a result of this.

I agree. Im most cases, testable code also pushes your design towards the single responsibility principle (and also other SOLID principles like DI). And that's a good thing.

Collapse
 
dwd profile image
Dave Cridland

I think that "BECAUSE TEST!" is roughly the same as "BECAUSE SECURITY!" or the nebulous "BECAUSE UX!". What we're after is greater confidence in the software's quality, and quality is measured along many axes, often with a trade-off to be made. Focusing exclusively on test as an end-goal is a deceptive thing because software can be well-tested and completely useless.

So for some of my own green-field projects, I do very heavy automated testing - but I didn't need to have that influence the architecture to do so. It did influence the implementation, though - Spiffing, for example, is carefully written to avoid "bushy" branching, reducing the test effort required. The test framework is written to be data-driven, too, so that users can work with their own test data as well as mine.

On the other hand, some projects don't lend themselves well to automated testing at all - I've never seen good tests for the server-to-server portions of an XMPP server. Maybe it's possible with significant work, but I suspect it's one of those things more effective to write and manually test heavily. The bugs are complex sequential issues, difficult to replicate in any useful way in automated tests without having to write half a simulated network stack. So instead, my effort goes into manual test, and support for that.

Small pieces of code don't get tested not because they're unimportant, but because one can (hopefully) manually prove them.

So I'd note that:

a) Testing is a crutch we use to avoid provability. If we could usefully prove code, then testing it would be superfluous.

b) Testing only works if the tests themselves are correct. Testing is only useful if the tests are testing that which might fail.

c) The goal is not test. The goal is confidence.

Collapse
 
n_develop profile image
Lars Richter • Edited

I think that "BECAUSE TEST!" is roughly the same as "BECAUSE SECURITY!" or the nebulous "BECAUSE UX!".

It sounds so negative, when you say it like that. :-) But to be serious: In general you are right. Testing/Testability shouldn't be the main goal. No doubt here. Nevertheless, sometimes I make decisions, like the one mentioned in the post (introducing an interface), to make something testable. Nothing more. Just make it testable (or as you like to say it "BECAUSE TEST!" ;-) ).

c) The goal is not test. The goal is confidence.

And we should always keep in mind: A working test suite gives a lot of confidence.

Collapse
 
dwd profile image
Dave Cridland

Absolutely - a working test suite is a great way to get confidence. A working and audited test suite even more so.

Collapse
 
gregbair profile image
Greg

Definitely a B person.

Collapse
 
gonedark profile image
Jason McCreary

Is testability a reason to change your design?

Yes.

Collapse
 
hudsonburgess7 profile image
Hudson Burgess

Would you change your design just to achieve/improve testability?

I think there's somewhat of a hidden assumption that "changing your design" in the context of this question means "massively changing your design." That doesn't have to be the case.

I've found that refactoring lower levels of an application (more on that here) usually cleans up higher levels pretty quickly, without a lot of architectural overhauling.

Collapse
 
n_develop profile image
Lars Richter

I think there's somewhat of a hidden assumption that "changing your design" in the context of this question means "massively changing your design."

In this discussion, most people have this assumption. But, personally, I find myself in such discussions about small changes. Like the one in the post, when I'm just introducing an interface.

Collapse
 
lexlohr profile image
Alex Lohr

If testability and application of an API are opposed goals, the problem to be solved is not the testability, but the original design.

Collapse
 
n_develop profile image
Lars Richter

Premature optimization is the root of all evil

That's a real classic. :-) And it's so true. I totally agree with your checklist. But in a lot of the before mentioned conversations, there is an argument about "What is an accepted/important/valuable usage of the interface?".

Your first point on the checklist is

1) Is any one going to use extracted interface?

In most (of my) cases there are at least two usages of the interface. The first is the actual implementation. That's the obvious one. And the second usage is the stub/mock/fake (I don't want to start a discussion on the naming here) for the tests. But a lot of people would argue, that the mock-implementation doesn't justify the existence of the interface. And I don't really get that. Why would that implementation of the interface be less important than the first one?

And just one word about your last statement.

Never code for distant future.

Amen.

Collapse
 
ethansankin profile image
Eytan Sankin

Agreed. Testable code will make thing easier down the road and i think it should be a prime consideration. I'm always trying to use more and more functional programming principles to make my code easier to test. Untestable code will cause development to slow down.

Collapse
 
mykezero profile image
Mykezero • Edited

I think it's in "Clean Architecture" where Bob Martin says that a lot of programmers believe that the true value of the system is in its behavior.

Yes, it's the behavior which businesses value, but as programmers - the people developing the software - we need to be aware of the maintenance cost of code that's associated with choosing a design that's too locked down.

Maybe I have a mislead view of software development. I know I can easily fix code that behaves incorrectly, but has tests and is verifiable. What I can't do is fix locked-down code which has neither tests or logging.

That makes the software a black box where I cannot even begin to reason about what the software is doing in a production environment.

If the cost for an extra layer of verifiability is an interface, then give me the damn interface!

The case where I see a need for an interface is when testing manager classes.

Even though my component class is a simple domain logic class which does not use outside resources, my manager doesn't care what the implementation of that component is.

Why should I complicate my tests with the extra set up data needed to test drive the manager class by making it depend on the concrete implementation of a component class? The component could be very complicated in nature, requiring a very complicated data setup.

Of course, nobody but that one class will ever use that interface, but the interface here will lower the amount of work needed to create the test in order to verify that the system works as intended.

That is more than enough benefit to warrant the interface's creation.

Collapse
 
n_develop profile image
Lars Richter

What I can't do is fix locked-down code which has neither tests or logging.

That makes the software a black box where I cannot even begin to reason about what the software is doing in a production environment.

If the cost for an extra layer of verifiability is an interface, then give me the damn interface!

I could not have said it any better.

Thanks for your feedback, Mykezero.