7 ms·
Time is a dependency like any other, so use dependency inversion. (Which I think is advocated by TFA’s approaches 4 and 5.) Define an interface for timers, whi
by Xophmeister 3y ago
Time is a dependency like any other, so use dependency inversion. (Which I think is advocated by TFA’s approaches 4 and 5.)
Define an interface for timers, which is injected into the object that depends on time. Use a wrapper implementation over the usual timer for the real code; and a simple implementation, which can be controlled parametrically, in the tests.
- calebio 3y agoI agree. I first started doing this (instead of the often recommended global monkey patch) when I started programming in Go. In our main application we pass in a function that returns `time.Now()`, and in tests we pass in a function that returns whatever time object we want.
- BoiledCabbage 3y agoDependency injection of just the function you need is good, but is still the second best approach. The best approach is separating the calling of the external dependency (getting the time) to it's own function, and pass in the results of that call to your main function. Then test your main function. Ie approach 5. This eliminates the need for mocks and creates better factored code.
- theamk 3y agoThat works until you have a function that implements timeout and so needs to query time multiple times per invocation.
- JimmyAustin 3y agoThe function you pass in can keep track of how many times it’s been called, and provide different values on each call.
- Anon1096 3y agoThis is way way worse than passing in a timer and destroys your ability to write multi threaded code (among other defects).
- greiskul 3y agoThis is for unit testing. Even if your unit tests run in parallel, each test case will have it's own mocked clock. This is not even an experimental approach, the mocking of timers is literally one of the canonic examples of the advantages of dependency injection. I have written this exact code multiple times, on my own, or refactoring others people untestable code to this pattern to be able to test them. A couple of times the reason for the refactoring was specifically to demonstrate the existence of race conditions, to have a test case that has the race condition be deterministic so we could fix it and be sure of the fix working.
- BoiledCabbage 3y agoAgreed, so the preference order is 1. Pass in the data you need 2. Pass in a function that gives you the data you need 3a./3b. Pass in an object (or function like a factory) that gives you the function that gets you the data you need.
- micw 3y ago> Pass in a function that gives you the data you need That's basically dependency injection but on a per-method basis. I'd see this as an anti pattern because it makes function calling _way_ more complex and error-prone.
- berkes 3y ago> it makes function calling First: no, not necessarily. Second: when you limit yourself to function calling, then yes, the problem is more pronounced (but not always there, see 1) In OOP, you can have factories, or constructors, or even entire design patterns to help solve this. In FP, there are closures, that can solve this exact problem for you: the dependency -e.g. a timekeeper- is captured in the closure. Dependency injection is, by no means, limited to `do_the_thing(variable, depency1, depency2, dependency3)`. I see this argument too often used to counter the idea of DI, and it is silly: it shows above all that the person countering the idea has little experience with all the surrounding concepts to support DI. And that brings me back to 1: There's so much more that can be DI-d: objects can be instantiated with dependencies passed in, factories can do this. There are Actors, Workers, Decorators, Factories. Hell even a superglobal `config.get_timekeeper()` might work in some situations.
- LouisSayers 3y agoYou can extract the timeout functionality into its own structure then and mock it out during testing. There's usually a solution of some sort, it might just require a bit of rearranging and extraction of concerns.
- aaomidi 3y agoThat’s basically dependency injection
- LouisSayers 3y agoYes, that's my point - they're giving an example for why "you can't do it", and I'm saying even for the example they just gave you can. Create a Timeout class and subscribe to it for a given interval with a callback to what needs to be run. Pass it in as a dependency which can be mocked during testing. Not sure why the downvotes...
- deleted 3y ago[deleted]
- munksbeer 3y ago>The best approach is separating the calling of the external dependency (getting the time) to it's own function That is the FP maximalist approach and many of us reject it. So no, it is not the best approach. We use a time provider interface and mock it, and have zero problems with this approach.
- marcosdumay 3y agoThe FP maximalist is writing your code in a monad that implements a timer. It looks quite similar to the OOP maximalist people are pushing here.
- hnfong 3y agoSo, "yes, we're writing the same thing, but because monads I'm still pure and you're not"?
- marcosdumay 3y agoKinda yes. And technically, it's still pure. It does fit well a small set of problems and break in surreal nightmarish ways for everything else; just like the OOP's maximalist DI. It is worse in that you are injecting an entire interpreter instead of just a few functions, but this doesn't create as many problems on practice as it looks like it should. And it's better because idiomatically the injection tends to be explicit and well defined; and if your DI doesn't have at least one of those, it will again break in surreal nightmarish ways. (And here I have to point out that in OOP-land, web frameworks are allergic to well defined interfaces - what means that you can create one, but if you insist the framework tends to choke and die. So, if you are writing for the web in OOP, there's actually only one option.) But well, it's not surprising that the maximalist options all break in similar ways.
- strulovich 3y agoI used to think this way, but now I believe that any method requiring writing code in a different way than the more natural one for the sake of tests is suboptimal. People generally call time functions, and a good testing setup should keep your code the same (or mostly the same). Anything else makes testing less approachable, and requires extra time from developers. The provider and mocking (or a fake if you got one) approach is probably the best you can get with most programming languages.
- chii 3y agoThe unfortunate aspect is that this only works for code you control. If you have code you need to run, but has no control over (such as a database library), then you will have issues. For example, you want to test that an entry in a caching library is indeed expiring entries like you wanted. How do you change the library to consume a time interface, if that library doesn't expose it to you?
- theamk 3y agoIn C++, you normally don't -- there are low-level function hooking calls or ptrace api, but those are too complex to be used in unit tests. The approaches I have seen used in practical projects is: Ignore unit testing of time-related parts, rely on integration tests. Have unit tests be super slow so that actual system clock can be used. Get occasional false positive failures when your build system scheduled unit tests in parallel with particularly complex compilation tasks. Wrap everything in your own wrapper, providing "production" and "testing" version of class, which "production" calling into the 3rd party library while "testing" calls into your implementation that uses debug time. This is non-trivial as "testing" implementation might be quite complex, but may be worth it for some common functionality like timers.
- randomdata 3y agoYou don't. Testing third-party code is beyond the purview of first-party tests. If you have first-party code that is adjacent to a third-party library in a way that the library hinders testing of that first-party code, you would substitute a (minimal) implementation that you control in for the third-party library while under test. If the library is open source or similarly code accessible, you would fork it or coordinate control with its stakeholders to see the testing of it in that codebase (and refactor it as necessary to support that). This is where the library needs to ensure it does what the documentation claims. It is not the concern of your application. Otherwise, if the code is truly beyond your reach, you just have to trust that the vendor's claims are true. If you cannot establish that trust, use a different library that you can trust. Testing concerns are the least of your problems when you have no trust for the library you are using.
- 3y ago
- bluGill 3y agoThen you get too many dependencies and the code is hard to work with because you in turn have too add so many dependencies. This is a hard problem in large programs.
- noobermin 3y agoWhile I generally don't think tests are bad in general this is a wonderful example of the fundamental problem with software development. Essentially, you have a tool that was supposed to make development easier (tests) eventually change the code to become more complicated to make it easier to use that tool with, which makes development harder.
- skrebbel 3y agoInjecting a Clock or Timer is great not just for automated tests but also for debugging weird behavior. Theres really hardly any downsides.
- bruce343434 3y agoI'm not sure how that would work. Can you give an example?
- EdwardDiego 3y agoHave a look at this method on Java's Instant class for an example. https://docs.oracle.com/javase/8/docs/api/java/time/Instant.html#now-java.time.Clock- https://docs.oracle.com/javase/8/docs/api/java/time/Instant.... Code under test creates Instant, so long as it uses the Clock you've passed in, you'll control how it determines when now() is. You've got several built-in testing clocks like this one[1], but can also implement your own. [1]: https://docs.oracle.com/javase/8/docs/api/java/time/Clock.html#fixed-java.time.Instant-java.time.ZoneId- https://docs.oracle.com/javase/8/docs/api/java/time/Clock.ht...
- skrebbel 3y agoJust a class (or struct or module or whatever your language has for that) that has a “now()” method which returns the current time. And then replace all your calls that do Date.now() with clock.now() and then you can make “now” be whatever you want.
- onetimeuse92304 3y agoSometimes the simplest things are so hard for people to come up with. Unfortunately, people are trying to make their life more difficult than it needs to be -- I think developers are especially prone to trying to figure out their own solution rather than spending a moment to search for it. All contact with outside world should happen through interfaces with replaceable implementation. If you care about time related stuff, this should also include time, timers, schedulers, etc. Make sure you don't circumvent the interfaces by accident. There are stupid ways to do so like getting current time in SQL or embedding current time in new objects by default.
- crabbone 3y ago> Time is a dependency like any other Oh no... not at all. Here are some examples of when your approach turns out to be worthless: * Performance testing, eg. looking for memory leaks. It doesn't matter how your timer ticks, what matters is that over time the system performance degrades. * Any kind of hardware testing that relies on sensory input that is time-gated. * Even "simple" stuff like testing software that does eg. desktop video recording. Still, you cannot substitute the wall clock with whatever timer you will come up with. I mean, in all those cases you "sort of" can, but that will render your tests worthless. * Of course your home-made timers will prove useless if the tested software has third-party components that don't care about your timers. Of course some cases can be handled by what you described, but, in my experience, when code works with time, the tests need to concentrate on special cases and those are usually hard to impossible to emulate through home-made timers. Or, they would benefit tremendously from time being real rather than a "surrogate".
- damagednoob 3y agoNone of the scenarios you listed above are unit tests, which I believe is what the OP and the post was referring to.