How we took our tests from 100 seconds to 100ms
shyp.github.io
shyp.github.io
Vagrant has a mode called rsync-auto which will synchronize directories into a local vm. Or you could just always have the code local to the node process. It makes more sense to have your editor work with remote files.
Next biggest problem was relational database. I think that tests requiring a SQL database are usually a bad sign, and the statement "we'd like to use transactions but our orm doesn't support them" makes me sad. To paraphrase Ben Franklin, "those who would give up transactions for a framework deserve neither". Here's some ideas for speeding up your tests, in order from best to worst.
- Make most business code rely on a service or interface. Swap that our during tests for a trivial in-memory implementation.
- Use sqlite :memory: for tests
- TRUNCATE TABLE
- Set your database to running with scissors mode (disable fsync)
[edit] I just took a look at the ORM used, waterline https://github.com/balderdashy/waterline . Why wasn't step one moving to sails-memory for running tests?Then you run into issues where the functionality works on SQLite, but not on your real database.
The ORM can't abstract away all differences between databases. For example, SQLite doesn't have Numeric type, so all of the Numeric(10, 2) fields on (e.g.) my SQLAlchemy (a Python ORM) models will trigger warnings when saving to a SQLite database.
Strictly speaking it's also possible to get subtle differences in behavior across OSes. Especially if you have a race condition you don't know about yet. I always tend to encourage more diversity (carefully spent).
This strategy is, in essence, mocking the FS layer in DB tests.
In general we're trying to write and run more tests that only create and manipulate objects in memory, especially for testing business logic. Unfortunately we still have to have some level of integration with the database to test things at a higher system level.
Re: transactions, most of our database code uses the ORM, so we thought other test speedups would give a bigger benefit. We recently added an interface for getting/querying with a `pg` connection directly and we're using that to implement transactions in a few critical places. I'm planning to release the code soon - hopefully in a blog post in a few weeks.
Service layer tests should have mocks of the dao and web service calls. You should do most of your testing here and with unit tests for tricky algorithms.
Integration tests should hit the full stack, but those should run on a continuous integration server asynchronously and also take place in their own empty database.
The key to dao and integration testing is the empty database. Don't let it fill up with test crud between tests. PostgreSQL is pretty fast when your test db only has a couple of rows.
Some major bugs are caused by people not testing their code with enough data.
How can I really ensure my CHECK constraints / DOMAINs, unique indexes, RAISE EXCEPTION in insert/update triggers, etc. are correctly implemented if I'm not testing against a real Postgres database?
The unit tests that test out your business logic should use mock data - it's just much more reliable, reproducible, and faster.
It's about creating appropriately sized discrete modules.
There should be some way to combine things into a single module/file or otherwise glob it all together, while removing pointless test, docs, and Adventure Time animated gifs from your deps folder.
Although I'm sure there are module maintainers shipping AT gifs, we can't really blame npm for that.
- Run all your tests in a transaction, roll it back in the end (also avoids writes to disk in most scenarios)
- Put the DB file system on a RAM diskEnd to end tests are great for catching connectivity bugs though.
I highly recommend this brilliant talk by J.B Rainsberger on how integration tests are a scam: https://vimeo.com/80533536
If I had a dollar for every time I asked someone why they weren't fixing their shit, and they answered "Oh, I thought the build was failing randomly again." I'd have a lot of dollars.
Yes, there are bugs that look like bad luck, but are really flaws in your logic. I have fixed a lot of bugs that look Random, but eventually you're left with, for instance, selenium just refusing to click a button.
Reading the unit tests can help you understand an unfamiliar project by acting as documentation, telling you what is expected behavior.
If you have bad application code that's hard to read, what's to say that the tests will be any better?
After all, the important thing is whether your application/service/library does what it promises to its users/consumers. Whether some function/class deep inside works or not is mostly irrelevant (can help in fixing the issue faster though).
Unit-testing-first has a tendency to make the tests very fine-grained, often need changes when you refactor internals (how do you know test did not break?), and needlessly keeping rigid internal details that one should have the freedom to change. Have seen plenty of code being kept around because it had lots of tests - when it was not actually used for anything at all!
Note: Above approach is coupled with a pretty strict adherence to keeping projects small and independent, preferably <10kLOC.
You don't remember the last time a unit test caught a bug, because on average it is much longer ago and if it was a test for a unit you wrote this morning, you usually blame it on 'work in progress' and think you would have caught that trivial bug anyway.
I think the nature of units tests make it easy to underappreciate them.
What ended up happening is that the code quality ended up worse because they stopped running tests very often because there were too many integration tests and they took too long to run. Sad part is, a lot of those tests would have made a lot more sense as unit tests.
however, having a framework in place helps a lot when working at user submitted issues: those are the issue one should strive to replicate trough unit test. In a six month of production, you get a fairly robust suites of test on all corner cases undiscovered in development.
In the rush to shoe-horn tests into a taxonomy, or 'integration' vs 'acceptances' vs 'unit' we often loose sight of the actual value proposition. Assisting design and building confidence to make changes.
Obviously if you've walked the walk with unit-tests and done proper TDD and all that, you've caught the bugs before submitting your code.
Ofcourse the remaining bugs (which there will be plenty of!) will be found in your end-to-end testing where all the bits are tied together.
This is only natural and does is no way suggest unit-testing was a wasted effort.
Unit testing and TDD are quite separate things. Perhaps you should try writing unit tests after you write your code and then you can come back and add value to the conversation.
Nice snark there. Feel free to keep it to yourself.
To add actual value to the conversation (as opposed to your contribution), I can very much recommend the book "Working Effectively with Legacy Code"[1] for how to handle unit-testing in the scenario of existing "legacy" code-bases.
It's full of useful tips and methods to get testing in place "anywhere" and has a pragmatical (as opposed religious) approach to getting it done.
To spark some interest: The book defines "legacy code" as any code not covered by unit-tests.
It may be seem dated (from 2004 and all), but it's been the most useful book I've read on unit-testing by far.
[1] http://www.amazon.com/gp/product/0131177052/ref=as_li_tl?ie=...
If you write the tests afterwards you can better gauge how well they catch bugs. It sounds like the parent has tried this and has found them pretty useless.
I've personally found that unit tests are not worthwhile for many components, and yet are critical for others.
I've found that there are way more books for "greenfield" software development and not so many books for what 60% of people actually do (maintaining other people's projects, legacy or otherwise).
For example, the following can be done without destroying the spirit of testing:
* Stub the sign in (big savings)
* Keep a tab on fixtures (big savings)
* Parallellize (big savings)
* Buy a better machine (big savings)
* Truncate with transactions/container-snapshots/etc (medium savings)
* Tune database for speed than safety (medium savings)
* Tune GC (medium savings), with enough RAM (16GB) disable it entirely (big savings)
* Disable logging and instrumentation, yes including timing measurements, unless intentionally needed (small to medium savings)
* Mount entire codebase, database and resources in tmpfs (small savings)
The below, while they might improve performance, go against the spirit:
* Tautological tests (http://fabiopereira.me/blog/2010/05/27/ttdd-tautological-tes...)
* Too many mocks/metaprogramming
* Using a different database driver
I'm planning to write a tool, which given a Class, will pump out lines and lines of tautological tests, line for line (a.a should == a.a), method for method (a.should receive save, a.save), branch for branch (with some metaprogramming). Coverage will report a cool 100%, tests will be blazing fast, and that should calm the lead devs down.
This is why the tests took 100 seconds in the first place. While I agree that speed isn't the number one priority for everything, Reducing the length of time your entire test suite takes to run to be doable on every single run of your framework is absolutely worthwhile.
There's always extremes.
If people write tests with no regard for speed (as I've seen) you can have "unit-tests" run for 45 minutes before completing.
That might be OK to guard against regressions on a nightly-build, but it will in no way be a test-suite I'm willing to running prior to every commit I ship.
If the test-suite runs in less than a minute, I'm much more willing to do so before submitting code, and in that way avoiding regressions which needs to be fixed later on.
If the tests runs in less than 10 seconds, I'm probably going to run them for every major change I do, even if I'm not planning on submitting anything yet, just for peace of mind.
There's IDEs where tests can be run automatically in the background as you make changes and let you know the second you broke something. For that to work efficiently, your tests has to be sufficiently fast.
So maybe the aim shouldn't be that tests must be fast above everything else. But if the test-suite can be run quickly and casually, it's value will increase tremendously.
I started migrating to AWS spot instances to make this whole monster manageable.
I'm sure others were doing the same before Ruby too. The wheel keeps turning...
I am admittedly a bit out of the Ruby loop these days, so it's possible that this has been improved. The fastest way of course is to not load a zillion gems...
An intern had thought that synchronizing things with "sleep one tenth of a second then check that flag again" at the heart of the product and reading and splitting by line a file where some lines might be 86Mo long were good ideas.
"Avoiding Sails isn’t possible in every situation, but we’ve found it’s a useful design principle - it helps us separate our business logic and our data storage layer, and will make it easier for us to move off Sails in the future."
Ooops.
For example, we disable `dynamicFinders`, don't use policies, don't use blueprints, don't use Waterline's associate/populate/destroy, don't use the "turn any controller function into a named route" setting, don't use globals, disabled all of the Grunt/websockets junk, and overwrote all of the error response handlers.
The result is that day to day we're writing something that resembles an Express app with its own `routes` file. We're looking to move off of Waterline/Sails entirely, it's slow going though since we're a small team and it's tough to prioritize that, as it "works" now.
Every company has parts of their tech stack that don't do a great job - we're somewhat fortunate this one is easy to mitigate.
Mostly I think we could easily switch to Spray, however currently I really liked Play in his current version.
There are much larger companies (Fortune 100) that use Sails.js. This isn't a knock against Shyp at all, and I'm glad they're using Sails, I'd just like to clear up whatever the source of your misconception is. Sails, Waterline, and the many related modules are all in active development.
(Disclaimer: I work for Balderdash, the company that created Sails)
Anyway, I can't imagine how running unit tests could possibly reach 100 seconds. Usually, that means that the tests are coupled to the environment (database, filesystem, ...). So without being able to read the article, that would be my guess. There are tons of good (and old) reasons why you should decouple unit tests from the environment as much as possible. Execution time is only one of them.
I don't think it's your fault, though. Chrome loves crashing and throwing away all of my research tabs. That wasn't the first time. It's only the first time I could reproduce it with a specific website. Still, as I said, I don't think it's your fault.
I'll just finish reading the article when I'm back at my PC. Test optimization is a great thing.
then tests take 10x the build time and eventually impact productivity like crazy :(
or when they shouldnt but do, theres a large amount of work to change that
For this reason I have wrote a small, but useful tool called require-time.
https://www.npmjs.com/package/require-time https://asciinema.org/a/21275
Is it just me, or does that seem like running a lot of tests per day. I mean, how many lines of code do they write per day? 1000? Do they run the test suite ever time they write two lines of code?
furthermore, your code is then only as good as your tests. And thus your code become vulnerable to failing on anything outside the test cases.
While speeding up the process is always good, tools like lucifer or spork are only masking the problem: loading the whole environment for unit-level tests. Sails, like Rails, promote tight coupling between all parts of the system, which harms test speed, thus harming another development tool: TDD.
The more decoupled and isolated you write your business logic, the easier and faster it is to test drive it. Then you simply plug an HTTP interface (with routes and whatnot) that calls into your core.