6 ms·
Mocks, stubs, shims, etc. are usually just hiding the fact that the code to be tested is terrible, even in OOP languages. Furthermore side effects have nothing
by ignorabilis 11y ago
Mocks, stubs, shims, etc. are usually just hiding the fact that the code to be tested is terrible, even in OOP languages.
Furthermore side effects have nothing to do with testing one's code. Why? Because you have to test your own logic, not I/O or something else. I/O is already tested by someone else. So you actually don't care where the data comes from - the filesystem or a hardcoded string - you care if the output is correct after the respective transformations are applied to the input.
And when you think about it testing shouldn't be that needed at all. In the OOP world, where state and identity are helplessly tied together and we are not working with values, but with references to values, tests might be a necessary evil.
In the land of Functional however writing tests shouldn't be needed, at least in theory. Why? Because if you write small composable functions you should be able to test them right in the REPL. Corner cases? You have to think about them right from the start. TDD forces you to do so - then why shouldn't you when you have the immense power of a REPL? Once a function is ready you are not supposed to change it much. If you do you should change the inner workings and not the input/output. If you need to change the i/o most probably you need a different function. But what is more important is that if you needed to change the i/o and had tests for this function you would actually need to change the tests as well, which is more work (double? triple?) and no added value.
- awinder 11y agoHonest question: how do you validate business rules are adhered to without defect in FRP and how does FRP differ from OOP in that regard?
- 15155 11y agoStolen from Wikipedia: "FRP is a paradigm using the building blocks of functional programming." I doubt OP was referring to FRP. In any case: in a functional programming language, you'd just unit test the building blocks (functions). Assuming every function is pure and total, these unit tests should be succinct and mirror your business logic quite closely.
- ignorabilis 11y agoActually the OP uses Clojure in his project.
- marcosdumay 11y agoNot with unity tests, that's a certainty. Unity tests do not test business logic. Yes, when people say that no tests are needed, it's hyperbolic. Tests are always needed, but you do not need to test all corner cases, just the ones created by business logic.
- maehwasu 11y agoI write fairly large backend systems that are continuously growing and facing evolving requirements. I follow those principles, and have yet to write a single test. It's just so much more robust to separate I/O code from other code, and verify that the building blocks of the system work. I do this in Clojure, but am looking to migrate to Haskell soon to enforce this even further.
- ignorabilis 11y ago+1 for Clojure. I suppose you will be migrating to Haskell because it is a PF language. What would you miss most from Clojure once you migrate?
- vutekst 11y agoI have used both languages quite a bit. What I miss from Clojure when doing Haskell is having access to a seemingly infinite collection of libraries (thanks to the JVM/maven/etc). There are of course many libraries on Hackage but the JVM ecosystem dwarfs most others.
- lgas 11y agoHave you considered/tried out Frege? https://github.com/Frege/frege https://github.com/Frege/frege From the github page "The similarity to Haskell is actually strong enough that many users call it 'a Haskell for the JVM'."
- trjordan 11y agoI disagree that I/O can be totally decoupled. The API that I/O functions generally adhere to is too weak for your application: on error, what do you want to happen? This pushes "I/O" functionality back into your logic, and that requires testing. In this specific example, if you write the first file, but fail the second, you might want to undo the write to the first file. Or write another file to say you failed. Or write to a log. These sorts of things are important, and mocks can be a good way to fake it.
- ignorabilis 11y agoYou should be testing your own code and not catching exceptions. I/O will either return an error or the string you need. There is no third option. So put aside the I/O - concentrate on testing the string transformations and on testing the logic that handles the error. Btw in functional languages catching exceptions is not idiomatic, it breaks the functional approach. In this example you are not supposed to test undoing the file. Undoing (deleting) is what I/O does and this part is tested by someone else, somewhere else. You only need to test the arguments that are passed to that specific I/O function. If they are correct your program is correct. If they are hardcoded you shouldn't be testing them at all. You may want to write a log. Again, test what you are about to pass to an I/O function. Don't test if the file is actually written. If you supply the I/O function with the correct arguments and the function fails to write the file, the issue is not in your code - search for it elsewhere.
- arielby 11y agoThat's why you mock the IO - to make the IO call return an error.
- ignorabilis 11y agoYou don't need to mock it. In your tests just manually call the code that is going to be executed if the I/O call returns an error. This goes for both OOP and FP.
- AnimalMuppet 11y ago
- stcredzero 11y agoOnce a function is ready you are not supposed to change it much. If you do you should change the inner workings and not the input/output. This presupposes that you will always come up with an adequate design, and that you won't have to refactor this library of functions. For large enough systems, this is almost never the case. For sizeable systems in production, you will always come up against things you haven't thought of before. Granted, proper factoring of your functions in the first place is going to help a lot. (Small composable functions.) However, if you claim that you can code such that you never ever have to change your mind about function signatures, never have to propagate changes across the system, even in large production systems, then you're either not seeing something, or you have some technique which you should be selling or basing a consulting practice around. (And/or, maybe I have something new to learn.) EDIT: I have written projects in Clojure, including a multiplayer game server. This is the experience I'm basing this comment on.
- ignorabilis 11y agoIn the previous post I started with in theory. What this means is that theoretically things can go like this and in practice they have gone for me until now (with Clojure, so +1 for you too :)). It doesn't mean that it will work for everybody or even for me in the future. Then: Granted, proper factoring of your functions in the first place is going to help a lot. hence -> Once a function is ready you are not supposed to change it much. much not= at all. And you are absolutely correct - eventually you will have to change some of your functions. And when you need to do so you should focus on the logic and take your time until you understand it completely. Only then refactor the funciton. So the emphasis should be on the developer completely understanding the task and not on the developer finding a cheap way to change the code and rely on tests to catch his errors. Furthermore if you change the signature of a function, tests are just another place where you need to change the function as well. In practice the tests that you wrote are not doing you any good because you cannot test the reinvented function with the old set of tests. Sure, tests might catch a few errors here and there at some point. But after all what is more time consuming - maintaining hundreds of thousands of tests or focusing on your single, small, composable function and your problem? I am part of a team that develops a very large .NET (with C#) product with hundreds of projects in the solution. So far the primary use of tests is to be maintained. They caught a few bugs, true, but nothing that our QAs were not going to see anyway. In imperative OOP land tests are a necessary evil, but with time I started doubting even that thesis.
- chriswarbo 11y ago> tests shouldn't be needed, at least in theory Tests are very useful for distinguishing between "what a function does" and "what you think a function does". Especially when you're building on functions written by others. Types and documentation help too, but if someone's already misunderstood something, such descriptive information might still fit nicely with their incorrect world view. Personally, I can't live without QuickCheck :)