6 ms·
I disagree with the notion that making your code testable in isolation serves no other purpose than to write unit tests. It very specifically forces you to thin
by m12k 6y ago
I disagree with the notion that making your code testable in isolation serves no other purpose than to write unit tests. It very specifically forces you to think about how and why each piece of code is coupled with other code, and generally requires you to make this coupling as loose as possible, to make testing in isolation possible. Loosely coupled code is also easier to reason about and easier to refactor. So testing doesn't just provide you with the value of tests, it also nudges you toward a saner architecture.
- alecbenzer 6y ago> Loosely coupled code is also easier to reason about and easier to refactor. Can you expand more on this? I think this is where the author would disagree. E.g., how is the code easier to reason about or refactor having introduced a location service interface that has only one one implementation?
- projektfu 6y agoSuppose you have a unit that makes a couple of api calls to set itself up, then performs a computation, then does some sort of storage. Thinking about how you would test this might lead you to an inversion of control approach, and you might isolate side effects. The storage provider might get passed in, both to make it easy to mock, and to reduce coupling and surprise.
- alecbenzer 6y agoI meant expand on how those things make code easier to understand, not why unit testing causes you to adopt them.
- projektfu 6y agoMainly it reduces surprise. If you call an interface with implicit dependencies, you won’t know if why it’s breaking without debugging and making sure it sets up its dependencies properly. If you call a testable interface with explicit dependencies, you can mock out those dependencies to debug parts individually.
- barrkel 6y agoI strongly disagree for the reasons listed in the article; it induces the construction and testing of abstractions which exist solely to enable testing, and do not enable simpler reasoning. Refactoring is even worse. Refactoring after you've split something up into multiple parts and tested their interfaces in isolation is far more work. Any refactoring worth a damn changes the boundaries of abstractions. I frequently find myself throwing away all the unit tests after a significant refactoring; only integration tests outside the blast radius of the refactoring survive.
- gonzo41 6y agoMaybe your unit's are too big. Unit tests are tricky because it's about coming to a personal and team agreement on what a 'unit' of functionality is. I find the same issue in throwing away tests when I'm writing small scale integration tests with junit. Usually I'm mocking out the DB and a few web service calls. So those tests become more volatile because their surface is exposed more. But smaller level, function and class level tests can have a really good ROI and they do push you design for testing which makes everything a bit better imo.
- UK-Al05 6y agoIt's normally the opposite. Unit tests are too small. If you unit test all of the objects(Because their all public) then refactor the organisation of those objects then all your tests break. Since you've changed the way objects talk to each other, all your mock assumptions go out the window. If you define a small public api of just a couple of entry points, which you unit test, you can change the organisation below the public api quite easily without breaking tests. Where to define those public apis is a matter of skill working out what objects work well together as a cohesive unit.
- bpicolo 6y agoThe notion of a public API is really more fluid in the context of internal codebases as well. It's important to maintain your contract for forwards/backwards compatibility when publishing a library for a world. When you can reliably rewrite every single caller of a piece of code, you don't have that problem.
- dboreham 6y agoYou often end up reimplementing the "comsumer" module for your code in order to test. This is problematic because a) extra work and b) that fixture layer probably doesn't behave exactly like the real caller code and c) now you have to keep those two implementations in sync.
- HelloNurse 6y agoThere shouldn't be anything particularly complex in test code, limiting the extent of any "reimplementation". Moreover, if the test client is different from real clients and not "in sync" it's a good thing: unit tests that do something differently within the limits of documented acceptable behaviour expose assumptions and bugs. For example, suppose outputs should be sorted in a certain way, but they are sorted if a client presents sorted inputs and not because they are actually checked and sorted: a test with random inputs can expose the hole.
- teknopaul 6y ago+1 Striving to make your code testable is almost always worth it. Someone might ask this guy to add some error handling to his code for example. :) Then he will find out that by writing code, however simple, that a "works on my machine" I.e. is proven to work in a single happy path context is painful to change. Writing code that runs in multiple contexts (composed as an app or decomposed for testing) is intrinsically more easy to work with and change.
- jordanpg 6y agoPut another way, if your code is very difficult or complicated to unit test, you've probably abandoned best practices for the language you're writing in somewhere along the way, in the name of expediency.
- tonyedgecombe 6y agoin the name of expediency or productivity.
- alecbenzer 6y agobut BeSt PrAcTiCeS
- indymike 6y agoThe exercise of writing debugable and testable code is often worth more than the unit tests.