|
|
|
|
|
by kortex
1279 days ago
|
|
Agree on the points that you should never need to abstract over your database, orm, message queue, etc. Disagree on dependency injection. I came from the globals/patch everything school of python, to the Fastapi/Pytest DI flavor, and it's a breath of fresh air. It's just so much easier to abstract the IO providers and swap them out with objects tailored to the test suite - eg for database, I create db objects which roll back any transactions between tests. Hard disagree on unit tests. Maybe in other languages, but in Python, trying to develop even a moderately complex app without unit tests is a nightmare. I know, I've lived it. Even in an app with >85% unit test coverage, there was still a ton of friction around development on any of the interfaces which had low coverage. Any gains in velocity of development almost always cost far more in debugging down the road. I love python, but it is really prone to dumb footguns at runtime, NoneType errors in particular. You need to impose a lot of discipline to make large python apps enjoyable to develop on. |
|
I think DI makes a lot more sense in languages that are statically typed. In Python, the implementations I've seen use a lot of ABCs, "_in_mem" repos (for mocking/testing). I've been assailing mocks elsewhere, but my criticism of ABCs is that you write a bunch of boilerplate code, only to still just get a runtime error that your tests should catch anyway.
DI also pushes IoC... everywhere. There's not really anything inherently wrong with it, but the fact that it's backwards from typical control flow makes it confusing because the two always coexist. As a result, when trying to trace the behavior of code, you have to dig through lots of layers of configuration and/or implicit magic to discover what implementation is actually being called. Or, you're lucky and there's only ever a single implementation (this is most cases), but then why are you using DI in the first place?
FastAPIs handlers kind of blur the lines of DI. The way they've implemented route handlers conflates DI with callbacks. They could easily have done something like what Django does, and generated API docs by the type signatures of the handlers, then it wouldn't look so much like DI, just mapping URLs to handlers.
> eg for database, I create db objects which roll back any transactions between tests
I've experienced the pain of implementing this myself in Go, so I know it's not super trivial to set this up yourself, but that said, I get irritated when I see testing influencing design decisions in ways like this. Like, this is a big architecture decision made solely to support an additional database configuration. My opinion is that this conditional belongs in some kind of `if TESTING:...` block during app initialization, not literally injected into every route handler, etc.
> Hard disagree on unit tests. Maybe in other languages, but in Python, trying to develop even a moderately complex app without unit tests is a nightmare.
Oh I've had that pain too, but types have pretty much solved those issues for me. Or, weirdly I've only encountered:
- apps with (effectively) no tests
- apps with only unit tests
- apps with both unit and integration (and maybe UI) tests
I've never encountered an app with only integration or UI tests, but in my personal/contracting projects, I only ever write integration tests, and it's worked great. Coverage stats help a lot here too, you can see which code paths aren't taken and either fix the bug or delete the cruft.
No tests is clearly very bad, but I think integration tests are far, far better than unit tests.