7 ms·
This statement made me think: "The database should be part of the tests. Do not stub it". Databases are both 1) complex piece of software and 2) hard to stub.
by eb0la 5y ago
This statement made me think: "The database should be part of the tests. Do not stub it".
Databases are both 1) complex piece of software and 2) hard to stub.
5-10 years ago having a database for testing was expensive.
Today you don't need to pay licenses for most databases, and you can spin a disposable one in a container. Easier than mocking the DB itself.
- yakshaving_jgt 5y agoWhy would the database be particularly hard to stub?
- buzzwordninja 5y agoA database is not simply a bag of bits. It has behaviour which matters to the rest of the program.
- yakshaving_jgt 5y agoThat's certainly true, but in a typical web application it's quite common that the specific behaviour of a given database isn't interesting enough to warrant testing so rigorously. In those cases, integrating with the database is perhaps a less sensible choice economically.
- hooby 5y agoBy my personal experience to make TDD work (where you make many small changes, run the tests, make more changes - step by step), tests need to run FAST - as in ideally less than 1 second. Even if it is easy to spin up a container, seed a database inside that container and drop and recreate that database after every single test (so that tests remain a unit and can't influence each other) - it tends to take a few seconds, which instantly makes TDD tedious to do. Thus you start making more changes at once, run the tests less, and drift away from doing TDD... The most typical solution is to run databases in-memory rather than in a container. My personal approach though is to make my code more modular, so that only the "database accessor" class needs to mock the actual database, while the "query builder" needs to mock only that database accessor (much easier) - and the database class needs to only mock the query builder (even easier) - and the actual application code needs to only mock the database class (super easy).
- WolfOliver 5y agoIt is not hard to isolate and stub a database the problem is you will not catch a lot of errors, e.g. writing a 40 char string into a VCHAR(10), DateTime conversations, ... In Ruby on Rails it is quite common to use a real database in the unit tests. Even before containers came out and it is still fast (yes, less than 1 second). In rails you use the same database instance for all the tests, so you do not have spin up a database instance for every case. Instead you use a database cleaner. There also exists a JS implementation: https://github.com/khaiql/dbcleaner https://github.com/khaiql/dbcleaner
- krageon 5y ago> Even if it is easy to spin up a container, seed a database inside that container and drop and recreate that database after every single test You would create a container image with everything already prepared how it should be, and you destroy it after every run - that's already a lot faster than what you're proposing.
- alkonaut 5y agoIt can still be very expensive after a while. I assume TDD practitioners also practice pruning their test suites to keep them from snowballing indefinitely. If you don't, then eventually running hundreds of thousands of tests against a real database is painful both in terms of waiting time and resource usage.
- treis 5y agoIf you've got hundreds of thousands of tests you've got a bigger problem than a slow test suite. Solution is to go to a modular or service architecture to break the application into bits that can be reasonably tested.
- berkes 5y agoLike always, "it depends". If, for other reasons, I follow the Hexagonal architecture, replacing the PostgresProjectionsRepository with an MemoryProjectionsRepository is both trivial and easier. Replacing an S3LogStore with a StdOutLogStore just as easy and trivial. But, if, God forbid, I'm stuck with a tangled mess of Rails ActiveRecord Models that break every SOLID principle with both feet, are highly coupled to the database and often even arbitrarily stick business logic in the DB or in the models, then certainly: it is hard. And while this sounds like a rant on AR (it is!), ActiveRecord has its positive trade-offs, or, often, simply is there. Pragmatism dictates that sometimes the database is hard or impossible to stub and you're far better off just giving up and treating the entire directory of models+the-fully-seeded-database as a single unit instead. But, what the OP of the article overlooks entirely - and what many haters of TDD miss completely: the tests are yelling important information at us: the design is a mess, there is too much coupling, we lack abstractions, there's too much going on, we are building God-classes and so on. But, again, sometimes pragmatism dictates we ignore this and go for the bad design anyway. As long as we pro-actively chose to go for the Bad Design (rather than unknowingly grow into it) this is fine, IMO.
- cassiogo 5y ago> replacing the PostgresProjectionsRepository with an MemoryProjectionsRepository Not as simple if you are using any "advanced" PG feature. If you testing simple select statements thats probably fine.
- wlamartin 5y agoI don't follow this. My assumption for the repository interface is something like (language, error and domain agnostic): interface Repository<T> { FetchAll() T[] Fetch(id) T Persist(T) id } Why would the SQL statements be reflected in the behaviour that the interface is providing?
- berkes 5y ago> Not as simple if you are using any "advanced" PG feature. If those "advanced" features leak into your domain, you have tight coupling, dependencies and poor testability (the latter is screaming that you have the first problems above all). If anything, such advanced features are best tucked away behind abstractions. SuperMarketProjection { CompanyList: GetAllWithinTravelTime(Time) Company: GetOne(id) Boolean: Write(Company) } This "projection" can use postgis, may store data in json columns, might have advanced materialized views adjoined on read, and so on. For the adapter, (edit: for the user of the adapter) it matters not. Which has only benefits. And one tradeoff: it requires carefull design and thought, which requires information that you often lack at time of designing.
- tyingq 5y agoYou will, though, end up fighting the system in some ways. CI/CD systems, unit test frameworks, configuration data approaches, etc, aren't equipped to (easily) spin up databases and connect to them. At least not in the more rapid cycle early end of development, versus slower cycling integration tests.