In this sense, mocks are solving the problem of having code that is full of dependency-injected AbstractFooBarFileSystemWithExtraPickles style of code, which is considered to be pretty un-Pythonic. I spent many years with Java and Spring so I can attest to both sides of this equation.
Those of us using Python are using it because it is an interpreted, dynamic scripting language. If I'm coding in something more rigid like C or Go, then I'd expect to have a more complex architecture in order to achieve things that are fairly simple in a scripting language.
Or easier - as the abstraction can simplify the interface, localize the logic for directory work, make it easier to port, and so on. That's the reason why most of us use "requests" and not urllib, too, for example, or Yoda and not Java's legacy time mess.
>a dependency injection system is then necessary in order to have the correct concrete implementations set up at runtime.
You can write alterative implementations (whether with base class, interface, traits, or what have you) and use them for production code and for testing without any dependency injection.
>Those of us using Python are using it because it is an interpreted, dynamic scripting language.
The abstractions mentioned come from Smalltalk, which arguably is the same or even more dynamic than Python. Having a class/interface/protocol/trait and a test implementation is by no means a static typing/Java thing, or counter to a dynamic language, as implied here...
Dependency injection can be as simple as:
class Printer:
def run(self):
print("hello world")
class Caller:
def __init__(self, printer_class=Printer):
self.printer = printer_class()
def doit(self):
self.printer.run()
In your tests all you have to do is pass a different object to your Caller class. No need to mess with factories, interfaces or Impl suffixes.If the point is just having simpler testability and making dependencies more explicit, this gets the job done.
And since Python is a dynamic language, you can use this with stdlib classes too, so it solves your first complaint.
I've seen it a lot in Ruby and C# lately.
It depends, right? If you suddenly wanted to let your existing set of classes read their inputs not from disk, but from some other kind of storage (e.g., files embedded in a Zip file), you'd only need to write one extra class and you're good to go. That would be a lot harder if your code called os.* directly.
> a dependency injection system is then necessary in order to have the correct concrete implementations set up at runtime.
If by dependency injection system you mean invoking one extra constructor in, say, main() and pass the object along as a handle, sure.
This allows me to do less work, write and maintain less code, and have better test coverage. It allows my code to be more fully tested even when it has not yet been abstracted, if that's what's in store for it. Patching local imports within the scope of two lines of code is a non-issue thanks to Python context managers. I have much more complicated examples of code that was already plenty complicated and mocks allowed me to get it tested quickly and effectively, instead of having to break it out into even more complexity.
Basically mocks have been all productivity and no downside for me whatsoever, using the Python standard library mock which is extremely well designed.
Alternative: have an abstract base class that describes a file system API. Have one implementation that builds on top of OS primitives and have another one that is a mock (potentially auto-generated through some mocking framework). That way there is no need to monkey-patch standard library functions at runtime.
I did that within a project of mine, written in Go:
https://github.com/buildbarn/bb-storage/blob/master/pkg/file...
https://github.com/buildbarn/bb-storage/blob/master/pkg/file...
Pretty slick that I can now use https://github.com/golang/mock to automatically generate a mock of that.