Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Lot of jargon and nothing new in terms of ideas. Again testing nothing but fakes and trivial interactions.

Here's my opinion test your code with all its side effects and infrastructure.

To do that fast and deterministically, switch to faster lighter weight versions of your production dependencies when running tests.

For example use SQLite or PgLite for databases, use in memory file system, use Mailpit for emailing etc.

Simulate your infrastructure dependencies, inject faults into them, test the real deal, SQL, file system semantics, protocols.

Don't leave the hard part of your testing to Q/A and users.



IMO you should have a healthy test pyramid which contains both types of tests. At the very top should be a smallish set of tests that are as real as possible and it's ok if they are a little slower. At the bottom you have more fine-grained tests that focus on one unit (classes != units) and tests it quickly and in isolation. Ideally we get this isolation by designing good interfaces (not necessarily in the Java sense) and can just not hook it up to real collaborators (maybe we pass callbacks to the email API or have boring glue code that just passes the return value on to the API) but sometimes a test double is ok. If we use a test double for network interactions to third parties, it's another great idea to replay recorded responses with something like Ruby's VCR which at least allows us to exercise the client code in medium-priced tests.

This allows you to get very quick feedback while in the weeds but also gives us comprehensive test coverage.


I agree. Arguments against the classical test pyramid are common, but IME they often come from a place of laziness. It essentially boils down to "unit tests are a chore to maintain, hence ...<hand waving>... integration tests are more valuable". Laziness _is_ one of the three virtues of a good programmer, but these arguments completely miss the mark. I would actually go a step further and argue that their popularity is doing tangible harm to software quality.

For a program to be robust it needs to be tested at all layers, and the testing pyramid is still a good rule of thumb to follow. Designing the program with testing in mind is crucial, and as you say, interfaces and their equivalent in any language are the right approach to achieve this. There was a period of time when monkeypatching was popular in dynamically typed languages, and perhaps still is (I've been out of the loop for a few years), but we always knew that it was a dirty trick, and that the software should be architected better to make it testable.

If anyone is curious what this might look like, I wrote a small project in Go[1] last year that is built from the ground up with testing in mind. There's a global `App` struct that is initialized in `main() `, and serves as the main container for all I/O the program does. This means that internally no component ever makes direct OS calls, but does it via interfaces, which can be easily mocked in tests. This makes unit and integration testing a breeze, and allows other niceties like passing down a global logger. I've adopted this pattern in all Go apps I write from scratch, and advocated for it on existing projects I've worked on, and it's worked great so far.

[1]: https://github.com/hackfixme/disco


Interfaces are only useful if there is reason they won't change otherwise they get in the way. Reason is often policical - this is where another group talks to my code and so the less we have to know about the other the better (not that the code is hidden just that you ignore it) reason could also be it is used everywhere so any change would be months of effort to make - longer if anyone else needs to impment anything.

if change is easy then better to make the change - you never really know all future requiresments so getting it right is impossible.

unless getting it right is possible. if you are implementing a new language you should know enough to get the interface to your strings right on the first try. That is not the normal case. (Strings will soon become impossible to change)


I very much agree- but swapping out the database is a scary operation if you use the database for any sort of nontrivial logic. You need to make sure that your unique indicies, partial indicies, constraints, views, triggers, etc. work just as well. Another option is to start each test in an isolated transaction and roll back at the end instead of committing, but that comes with its own set of headaches too.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: