May 16, 2020 04:44
@user2768 The general terminology for this is an XY problem (asking for help with Y (e.g. filenames), when the real problem is X (tracking changes consistently))
 

 Discussion between Warbo and Ewan

Imported from a comment discussion on softwareengineering.stac...
May 31, 2018 19:32
We went with the Executor implementation, since my suggestion of using functions "wasn't OOP" (despite my protests that (a) that's not an end-goal and (b) they're instances of Closure) and we "might need to extend it in the future" (my YAGNI protest was dismissed, as well as my proposal to replace the use of strings with a custom TextSequence interface in case we might need to extend it in the future) :P
May 31, 2018 19:28
Each of those classes was used precisely once, and the Executor interface only defined one method: execute. I pointed out that he'd spent a week reinventing the idea of a function, which are already first-class values in PHP (instances of the Closure class).
May 31, 2018 19:26
That same developer also spent a week defining the API we would use to represent RabbitMQ jobs. The result was an Executor interface, with implementations like EmailSenderExecutor, StoreCheckoutExecutor, etc.
May 31, 2018 19:17
@Mr.Mindor At an old job a new senior dev started and began introducing all this class-decoupling stuff, although he did so under the banner of "behaviour driven development". The ironic part is that as far as I can tell, BDD was invented/defined specifically to avoid the architecture astronautics that its creator had found festering under the "unit testing" umbrella.
May 31, 2018 18:15
Plus it violates pretty much every principle of object oriented programming, e.g. encapsulation says that we do not care how a method is implemented, whether it's all in one class or spread across several.
May 31, 2018 18:13
Having a method in one class call a method in another class is not a side-effect. It's just object oriented programming. There is absolutely no reason to "isolate" one class from another, whether that's using DI or anything else. Attempting to do so just creates a lot of extra work for no benefit, makes tests more fragile and makes the test results less representative of what the actual program is doing.
May 31, 2018 18:10
If there are no side-effects in the business logic, there is no need for DI. The business logic can be unit tested without DI, and the unit testing will be simpler and less fragile than if DI were used.
May 31, 2018 18:08
DI is not the only way to avoid problems with side-effects in business logic. Another approach is to just avoid having side-effects mixed in with business logic, for example by representing requests as first-class values and passing them around in pure, side-effect-free ways rather than executing them.
May 31, 2018 18:06
DI is one way to mitigate the problems of burying side-effects inside business logic. One cost of this mitigation is increased complexity, which is a bad thing.
May 31, 2018 18:04
DI is useful if there are side effects (e.g. REST calls, DB access, etc.) buried deep inside business logic.
May 31, 2018 18:04
Please reread my first comment: claiming that unit testing requires DI is nonsense. Claiming that avoiding DI is avoiding unit testing is nonsense. Claiming that people who avoid DI must think unittesting is bad is nonsense.
May 31, 2018 17:59
@Ewan I don't understand. Using DI to prevent tests from having side-effects is sensible. Using DI (or anything else) to prevent any class from ever using any other class is just nonsense.
May 31, 2018 17:59
@Ewan Wasn't trying to avoid, I gave 2 answers. 1st is use DI, since that's what it's for (avoiding I/O side effects); note this doesn't mean DI should be used for internal, unobservable, side-effect-free calls (like the Order and Price example). 2nd answer is make requests first-class values and return them, rather than executing. An (overly) simple example is returning strings of HTTP or SQL: unit tests are easy, no DI/mocks/etc. just regex. Integration tests would check we can send/receive HTTP strings, or run SQL queries, in general, but that is independent of the business logic.
May 31, 2018 17:59
@AntP I agree. Worst case I've run across is ServiceControllerServiceProvider. Also related to cultural appropriation from the Kingdom of Nouns
May 31, 2018 17:59
@Ewan BTW if Price is buggy then I absolutely want my Order tests to fail!
May 31, 2018 17:59
@Ewan REST calls are side-effects. DI is a good way to unit test code which mixes up logic with side-effects. Personally, I would prefer to disentangle the logic from the side-effects, i.e. have pure business logic calculate which calls to make, and return first-class values representing those calls. Unit testing is then trivial (just check the return value), no need for DI.
May 31, 2018 17:59
@Ewan "technically yes it fails the definition of a unit test because you test the Price constructor as well as the Total() logic" Which definition? The one you linked to says absolutely nothing about methods calling other methods, classes, etc. (and quite right too!) At one point it mentions that tests may conflict if they share a database, which we can avoid by mocking.
May 31, 2018 17:59
@Ewan Order models an order, as found on an e-commerce site for example. It is not a PriceFactory, since prices are assigned by product managers not manufactured in factories, and in any case for e-commerce purposes we would abstract away the details of manufacturing behind some simple model like Supplier... I'm joking of course! Yet the joke has a ring of truth: code should model the domain (orders, products, customers, ...); when code models code (factories, controllers, executors, initialisers, ...) it's an indication that we're solving self-imposed problems.
May 31, 2018 17:59
@Ewan Your link gives a good definition of unit testing. By that definition my Order example is a unit test: it's testing a method (the total method of Order). You're complaining that it's calling code from 2 classes, my response is so what? We're not testing "2 classes at once", we're testing the total method. We should not care how a method does its job: those are implementation details; testing them causes fragility, tight coupling, etc. We only care about the method's behaviour (return value and side effects), not the classes/modules/CPU registers/etc. it used in the process.
May 31, 2018 17:59
@Ewan I completely agree with that link. It doesn't support anything you've said, but does support claims from me & others, e.g. my Order example tests a "smallest testable part" (total method), it's independent/isolated from other tests (instances only scoped to that test), etc. Link says mocks can "assist" unit testing & give example of making tests independent by mocking a DB. That's a justified use-case for mocking. They don't talk about DI, "dependencies", decoupling every class from every other class, etc. In other words DI/mocks are techniques (of last resort), not goals/principles.
May 31, 2018 17:59
@Ewan I don't understand "technically": if some definition called that integration testing, I'd say that definition is flawed. That's also not "on the line": it's common OOP practice, modelling a domain using objects and their interactions, to create a language/algebra for expressing our problems and their solutions. Distinctions like struct/not-struct are either just leaks in that abstraction or irrelevant implementation details. A much more important distinction is whether or not a method has side-effects (which are domain relevant, e.g. "charges a card", "registers an account", etc.).
May 31, 2018 17:59
@Ewan Your characterisation of unit/integration too simplistic. If we have code like class Order { ... public Price total() { result = new Price(); for (item : this.items) result.add(item.price()); return result; } } and a test which does Order o = new Order(); o.add(i); o.add(i); assertEqual(o.total(), 2 * i.price()); you're saying that it's an integration test because it calls new Price rather than mocking it? I would call it a unit test since it's testing a unit of functionality (calculating the total price of an order) with pure input/output examples.
May 31, 2018 17:59
@Ewan Integration tests (and mocking) are for external dependencies: Web services, relational databases, key/value stores, email senders, etc. At some point this idea got hijacked and applied to "any other class, even if you just wrote it, it's literally right there in the same directory, would actually be in the same file if Java allowed it, and the only reason that code is in another class at all is because of the Single Responsibility Principle"
May 31, 2018 17:59
I disagree with your premise that unit testing and DI are so close. By using a mock/stub, we make the test suite lie to us a bit more: the system under test gets further from the real system. That's objectively bad. Sometimes that's outweighed by an upside: mocked FS calls don't require cleanup; mocked HTTP requests are fast, deterministic and work offline; etc. In contrast, every time we use a hard-coded new inside a method we know that the same code running in production was running during tests.
 
May 25, 2018 14:25
There's a difference between required functionality and future goals. Tests are for required functionality: they're precise, formal, executable and if they fail the software doesn't work. Future goals might not be precise or formal, let alone executable, so they're better left in natural language like in issue/bug trackers, documentation, comments, etc.
May 25, 2018 14:25
As an exercise, try replacing the phrase "unit test" in your question with "compiler error" (or "syntax error", if there's no compiler). It's obvious that a release shouldn't have compiler errors, since it would be unusable; yet compiler errors and syntax errors are the normal state of affairs on a developer's machine when they're writing code. The errors only disappear when they've finished; and that's exactly when the code should be pushed. Now replace "compiler error" in what I wrote with "unit test" :)
 
Dec 1, 2017 13:34
@Caleth In that GUI scenario, this question would become "why generate code at all, if the markup is sufficient?". For example, in an OOP language we might read the markup in a constructor, and use DynamicReception to expose a suitable API.
 
Nov 14, 2016 03:52
@EvSunWoodard If you're a computer, the isApp... function doesn't impact the overall program in any way. If you're a human, the isApp... function does two things: 1) tells you why there are two phone number branches (one's for production, the other's not) 2) why we're looking up the resourceId header (to see if we're in production).