5 ms·
You are completely wrong. Mocking is a huge design smell. The more mocks or integration tests your projects requires to get full coverage the less modular your
by crimsonalucard 7y ago
You are completely wrong.
Mocking is a huge design smell. The more mocks or integration tests your projects requires to get full coverage the less modular your program is. A program that uses many mocks is a sign of very very poor design. You will find the code more complex to reason about and much harder to reuse code without necessitating a lot of glue code to make things work together. Without proper knowledge you won't even know the program is poorly designed.
I will grant you that 90% of programmers out there don't know how to design programs in a truly modular way, so most engineering projects will require extensive mocking. In fact most engineers can go through their entire career without knowing that they are making their programs more complex and less modular then it needs to be. Following certain design principles I have seen incredibly complex projects require nearly zero mocking (very very rare though).
Mocking indicates a module is dependent on something. Dependency is different from composition.
Dependencies Composition
C C
+---------------------+
| | +----------------+ +-----------------+
| A | | | | |
| | | | | |
| +----------+ | | | | |
| | | | in | | | | out
| | | | -->+ A +------>+ B +-->
| | B | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| +----------+ | | | | |
+---------------------+ +----------------+ +-----------------+
What's going on here? Both examples involve the creation of module C from A and B.
left: 'A' exists as wrapper code around B and is useless on its own. To unit test A you must mock B.
right: every module is reuseable on its own. Nothing needs to be mocked during unit testing. No dependencies.
The only exception to the right example where you MUST mock is a function that does IO. IO functions cannot be unit tested period, they can only be tested with integration tests.
There's a name for the left approach. It's called Object oriented programming using inheritance or composition(the oop version of composition; not functional composition) as a design pattern. (both are bad)
There's also a name for the right approach. It's called functional programming using function composition.
I don't advocate that you strictly follow either style. Just know that when you go left you lose modularity and when you go right you gain it. All functional programming does is force your entire program to be modular down to the smallest primitive unit. Extensive mocking in your program means you went too far to the left.
tangent: Another irony around this world is that a lot of functional programmers (javascript and react developers especially) don't even know about the primary benefit of functional programming. They harp about things like "immutability" or how its more convenient to write a map reduce rather than a for loop without truly ever knowing the real benefits of the style. They're just following the latest buzzword.
- elsurudo 7y agoI mostly agree with what you're saying, but I will add that is is also possible to write well-designed, modular, easy-to-test (minimal mocks) OOP code. It does provide more guns to shoot yourself in the foot with, I will admit.
- crimsonalucard 7y agoYes you are correct. Check out smalltalk, it fits the paradigm you describe. It was actually rated the most productive programming language in the world according to namcook. Ironically, it's Definitely one of the least popular languages as well.
- tra3 7y agoForgive me, if I'm being dense, but doesn't either of these cases depend on how the composed objects are being used? In your functional example A is an input to B (or vice versa?), how do you propose testing one of the modules without first instantiating the other one?
- crimsonalucard 7y agoI'll give you two examples. One functional and the other OOP. Both programs aim to simulate driving given an input of 10 energy units to find the final output energy. #oop engine = Engine(10) car = Car(engine) car.drive() #result 8 class Car: def __init__(self, engine): self.engine = engine def ignite(self): self.engine.energy =- 1 def run(self): self.engine.energy =- 1 def drive(self): self.ignite() self.run() return self.engine.energy class Engine: def __init__(self, energy): self.energy = energy # ignite not testable without engine # run not testable without engine # drive not testable without engine and a car # ignite, run, and drive are not modular cannot be used without engine. # engine testable with any integer. # Car useless without engine # engine useless without car #functional \ def composeAnyFunctions(a,b):# returns function C from A and B. See illustration above. return lambda x: a(b(x)) def ignite(total_energy): return total_energy - 1 def run(total_energy): return total_energy -1 drive = composeAnyFunctions(run, ignite) drive(10) #result 8 # compose testable with any pair of functions # run testable with any integer # ignite testable with any integer # drive testable with any integer # all functions importable and reuseable with zero dependencies. # input_energy -> ignite -> run -> output_energy "I think the lack of reusability comes in object-oriented languages, not functional languages. Because the problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle." - Joe Armstrong you don't necessarily need the car or engine to simulate the energy output of driving.