Within the early days of Stack Overflow, we had been only one web site working a quick and lean operation. Stackoverflow.com was constructed for builders by builders as a small startup. Like all startups, we prioritized the high quality attributes that mattered most to us and let many others fall by the wayside, together with unit testing in keeping with greatest practices. The location was made for builders, and we discovered that a variety of customers had been joyful sufficient to report bugs and work round them whereas we mounted them.
Quick ahead to some years again after we launched Stack Overflow for Groups Enterprise. We instantly had a paid product that massive firms had been utilizing. In contrast to our group website customers, they didn’t need to discover bugs in manufacturing. We had integration check suites in place, however our testing infrastructure—particularly, our unit checks—lagged far behind the maturity of our product.
We’re now working to alter that. Finish-to-end and integration checks are effective and a part of a balanced testing program, however they are often sluggish. For those who’re seeking to allow test-driven growth (and we’re), in addition to rapidly check new options, then you have to be writing unit checks. I’ve been singing the praises of unit testing for good whereas now, and I’m excited to convey them to Stack Overflow.
This text will cowl what we’re doing to ramp up our unit testing program.
A refresher on check varieties
Earlier than we dive into how we’re including unit checks to our dev cycle, let’s go over the frequent check varieties. Right here’s how we outline the totally different classes of checks and their advantages and shortcomings.
Exploratory testing: This kind of testing lets QA engineers and testers give attention to what they’re good at: discovering edge circumstances and bugs. You give them early builds and allow them to bang on it till one thing breaks. Testers shouldn’t have in depth guide regression checks plans that they observe for every change/launch. If in case you have a mature set of e2e, integration, and unit checks that cowl the regression half, then you definately need your testers to search out bugs through the use of their creativity.
Finish-to-end (e2e): These checks simulate how an actual person would work together together with your utility, and due to this fact require a whole utility setup, together with networking, databases, and dependencies. You may arrange mock model of those, however usually they’ll use the actual factor. When e2e checks go, you may have a excessive diploma of confidence that the appliance works as anticipated—a minimum of for joyful path actions, edge circumstances and errors take a variety of work to check in e2e. On the draw back, as a result of e2e checks span the whole utility, they are often each sluggish and flaky.
Integration: These check how a function works with its dependencies. These don’t cowl the entire utility and are automated. Like with e2e checks, you should use mocks and stubs to stop actions like sending emails to prospects, however the level of an integration check is to check how a function works with dependencies, so think about using the actual factor when you may. Integration checks allow you to check actions like SQL queries that can not be examined with out accessing dependencies, and so they do it with out all the bags that comes from working a full setup. However something that checks dependencies might be slower and flaky.
Unit: There’s some debate about what precisely a unit check is. So we’re on the identical web page, we take into account a unit check an automatic check that doesn’t discuss to out of course of dependencies; it checks the smallest piece of code to make sure that it features appropriately. It simply checks a single course of and nothing else. Unit checks are quick and function independently of the rest within the utility. On the draw back, they solely take a look at a single piece of performance, so you would conceivably have all of your unit checks go whereas the function as an entire is damaged. They are often tedious to take care of if the check is simply too near the implementation of the function.
The large draw back for us, although, is that our historic structure has made it troublesome to jot down unit checks.
Greatest practices for testing recommend that we should always have a lot of unit checks, a medium variety of integration checks, and only some e2e checks.
As we had almost no unit checks, we needed to get transferring.
Why do we want unit testing anyway?
Chances are you’ll marvel why we’re including unit testing now—we’ve made it this far and carried out fairly nicely for ourselves, proper?
As we’ve talked about, we’re maturing as an engineering group. We have now paid merchandise that enormous enterprises pay good cash for. We have now a variety of new tech funding on our roadmap for the following few years, so we’ll want a resilient codebase that lets us refactor code when vital. To paraphrase, it lets us transfer quick with out breaking issues. Plus, refactoring the code for the checks lets us create a baseline of unpolluted code and implement the “clear seashore rule” for future code: depart the code as clear or cleaner than you discovered it.
Apart from the advantages for the code, it makes our total testing program higher and take much less time. We used to spend so much of time on guide regression testing and guide testing throughout pull request opinions. Automating these checks as unit checks will release a variety of developer time. And it will get us nearer to test-driven growth, which is able to allow us to proceed to ship new options to all three editions of our Stack Overflow for Groups product and our group websites even when these options require modifications to present code.
A great testing program results in a greater engineering program, so the trouble we spend creating unit checks would make our lives simpler (and extra productive). Clear, well-written checks function a type of documentation; you may learn by means of the checks and be taught precisely what the related code is doing. To encourage our engineers to work on check code once they work on product code, we wished everybody to personal the checks themselves, to be happy to alter and modify them as wanted.
There have been numerous specific anti-goals we had for this undertaking; outcomes that we weren’t attempting obtain in any respect. In constructing out unit checks, we weren’t attempting to create as many checks as doable, or to achieve a magical check protection proportion, and even to observe the testing pyramid strictly. There was no plan to run testing sprints or create checks for present code en masse or couple checks to implementation.
In brief, we wanted to get our code into form so we may construct checks simply, however we weren’t attempting to instantly have check protection on each piece of code already deployed in manufacturing. That is preparation for the long run; a lot of our code has been battle examined by our group of builders.
What we did
With the intention to create real unit checks, we wanted to make sure that any piece of performance might be remoted from its dependencies. And since virtually all the pieces that occurs on our public websites and Stack Overflow for Groups situations attracts knowledge from a database, we wanted a technique to point out to checks when to tug mock knowledge. We use each Dapper and the Entity Framework inside .NET to handle our database connections, so we created an interface that extends DbContext
in order that we will deal with mocked knowledge as a database connection.
Stack Overflow executes a variety of the identical queries again and again. As our website was constructed for velocity, we compile a variety of these queries within the Entity Framework. Compiling queries in opposition to our DbContext
interface was a bit problematic as a result of EF.CompileQuery
expects a concrete occasion of a DbContext
. We got here up with a helper class to make it simple for us to make use of compiled queries when focusing on an actual database and use in-memory queries when working unit checks. The question stays precisely the identical so we all know we check the proper conduct.
As soon as we had been ready to hook up with mock databases, we wanted to supply a technique to create the mock knowledge that’s a part of the check. So we launched a builder that may create mock website knowledge for checks. We’re utilizing builders as a substitute of constructors so we will change how these mock websites are constructed with out having to rewrite all of our unit checks. Builders assemble an object by solely explicitly passing the knowledge that you just want; all the pieces else makes use of defaults. Once more, we didn’t need to tightly couple our checks and implementation, so we selected to summary object development as a lot as we may.
Our hundred plus Stack Change websites and Groups situations share a variety of code, although the content material and design could also be totally different. These variations are managed by website settings, a sensible configuration retailer that may scale to tens of hundreds of websites with out utilizing up an excessive amount of reminiscence. To try this requires a database connection, so we wanted to make some modifications there as nicely. We had a settings mock arrange for integrations checks, but it surely was horribly intercoupled. We arrange an async context conscious injection step earlier than many of the different code hooks so independently working checks may initialize customized mock settings with out utilizing a database. As a further profit, this solved a little bit of flakiness we noticed from checks working in parallel, as they had been now not altering the identical set of mock settings.
We additionally need to have the ability to check our front-end code. For that, we carried out Jest, probably the most well-liked testing libraries within the JavaScript/TypeScript ecosystem. There are different strong testing libraries for JS/TS, most notably Mocha, however we’ve had good experiences utilizing it in our Stacks editor, so we determined to convey it in for all of our entrance end-code. Jest is feature-rich—it’s pretty opinionated and batteries included, so we may get began with it rapidly.
At level, we will begin writing checks. Based mostly on these modifications, we arrange a testing cookbook in our Stack Overflow for Groups occasion with particulars on how one can write good unit and integration checks, mock knowledge from databases, and cache testing knowledge. As a proof of idea, we created our first real-world check utilizing in-memory dependencies. Now we simply have to jot down extra checks.
Good checks make for higher code
Writing a great unit check is just not all that arduous. Writing good, testable code is. The perfect methods to attain testable code embody writing pure useful code with out dependencies. That’s not precisely doable in a contemporary internet utility. The second greatest means is to inject dependencies intentionally. Previously, we accessed a variety of objects from static contexts as a substitute of passing them intentionally, which made it very troublesome to create a testable model of that code.
With this, we’re committing to testability, to writing resilient code, and extra importantly, transferring rapidly to implement new options that our prospects and group need. We’re rising as nicely, which implies our code high quality turns into ever extra vital. Automated unit checks and testable code assist in all these areas.
Tags: testing, unit checks