4 ms·
If function A does not much more than calling B, maybe testing A is not strictly necessary. If A does something important/complex/critical/etc maybe the import
by pintxo 5y ago
If function A does not much more than calling B, maybe testing A is not strictly necessary.
If A does something important/complex/critical/etc maybe the important/complex/critical/etc part can be refactored into a dedicated function A1 without external dependency and then tested on its own? That way, the tests focuses on where the money is and gets easier to work with at the same time.
- adwn 5y agoNow you've added an additional, untested point of failure (the glue code), and you've made the interaction between A, A1, and B more difficult to understanding and more error prone.
- cloogshicer 5y agoExactly, fully agree. I don't understand why people advocate this insane over-abstraction and over-modularization. > more difficult to understanding I think this is the most important one. I hate those code bases where everything is split up into these tiny tiny units and you have no idea what the whole thing does. Reminds me of this: https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpriseEdition https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpris...
- berkes 5y ago> you have no idea what the whole thing does But you do have e2e, acceptance or integration tests, don't you? Whenever I see a need to prove, or documentation "how the hole thing does it", that is the moment to write an integration test: isolate "the whole thing" as much as possible, make it as small as possible, then fire some things at it, and see if the outcome works. It's not hard (but not easy either). The hardest point for those tests is to keep the setup and teardown sane.
- ehutch79 5y agoI have absolutely read that the only valid tests are unit tests.
- berkes 5y agoAnd I have absolutely read that evil lizard people invented COVID to inject us with microchips. Doesn't make it true. What I mean with this, is that there'll be many opinions and feelings posed as a fact. About testing too. Which is why I dislike the OP's article, as I pointed out elsewkere in the comments: the things he poses as "rules" (earlier on he calls them tips, which is better imo) aren't rules: they are highly controversial opinions. As would the statement you read be. Daily TDD, is more of an "art" than a science. It requires experience, and fingerspitzengefühl. Though, the science on TDD and BDD is strong and convincing, summarized in "Accelerate: The Science of Lean Software and DevOps: Building and Scaling High Performing Technology Organizations"[1]: TDD and BDD significantly helps to build better software. [1] https://www.amazon.com/Accelerate-Software-Performing-Technology-Organizations/dp/1942788339/ref=sr_1_1?keywords=accellerate&qid=1639394831&sr=8-1 https://www.amazon.com/Accelerate-Software-Performing-Techno...
- cloogshicer 5y agoI'm lamenting the complexity of those over-abstracted solutions. Your proposed solution (adding additional integration tests) is adding even more complexity onto the pile. This makes the entire system (of which those integration tests are a part of, when it comes to understanding) even more difficult to understand.
- berkes 5y agoAnd I'm trying to explain that complexity is not cause by over-abstractions, but instead, solved by it. Yes, certainly the integration tests are part of the codebase (allthough some people argue that tests are not part of it, I strongly disagree). But they are abstracted away. Neatly in their own directories. Using their own class and dependency hierarchies, having the proper dependecy-directions (the UI tests depend on the UI-layer, but the UI-layer does not depend on the tests). And so on. If you truly believe that the solution to hard-to-understand-complexity is to have less complexity, I'm afraid you'll be dissapointed: all projects will compe with ever growing essential complexity. Even if you avoid all accidental complexity, you'll have to deal with the essential complexity. And our main tool to do that is abstractions. From an OS abstracting the hardware away, to database-servers to libraries for that database-service and so on. The solution may be to say: I don't want a database (and can forego the db-lib connecting to the db-servers, which run on an OS) but if the database is essential complexity: abstract it away.
- cloogshicer 5y agoThat's fair - I'm not against abstraction in general. I agree that we wouldn't be able to handle the essential complexity of software without it. I'm just saying that each abstraction also has a cost, and makes understanding the whole thing more difficult. Abstraction is, in my understanding, a necessary but costly tool. A lot of my thoughts are summarized well by Brian Will in this video: https://youtu.be/QM1iUe6IofM https://youtu.be/QM1iUe6IofM
- pintxo 5y agoPlease accept that I made no argument for a-priory abstraction and modularization. Personally, I find any abstraction problematic, where I cannot understand from the import statements of the current code which other code will be executed. But there are the times where abstractions and modularization can make our lives so much easier. Who would want to dismiss the abstraction of the filesystem and work rather directly on the disc blocks? All I am advocating here is, that everything is a tradeoff, and we need to weight the costs of mocking some stuff against refactoring the code in a way that we can live without such a mock. And I have personally been at the point where refactoring some code into a separate function made it easier to unit test this specific highly complex functionality.
- cloogshicer 5y agoFair point, thank you. Used in moderation, the technique you explained above can be worthwhile. I just think it is overused, generally, and the costs are rarely considered. Sorry that my above comment was worded a bit harshly.
- pintxo 5y agoIt's all tradeoffs. Glue code should really only be tested in your integration or E2E tests, you have those, don't you? Now, if A would look like below, we might accept not testing it at all, as the initial and upkeep costs to mock B might be higher then the potential cost of the one-time failure in the internal logic of A. function A(args) { return B(args) > 1 ? true : false } But, if A looks more like: function A(args) { const result = B(args); switch (result) { case 1: case 3: return result * .5; case 2: return result * result / 5; } } I would probably look to convert it to function A(args) { return A1(B(args)); } enabling me to unit test A1 without thinking about B (and without the cost associated in mocking it)