If what I'm about to say resonates with you then read on.
- If your code is broken, you fix it and something else breaks, I can relate.
- You wrote an awesome project that you had to step away from for a month, now you can't remember your code, how it works, I can relate.
- You can't understand your coworkers crazy complex code, I can relate.
- You wonder how to learn another language and what they have in common? I can relate.
I am Adam, approaching my 10 years of programming, and for a good 5 of those years I had those questions, problems and frustrations. No matter how much I tried to be careful I'd break something. But if I threw enough mud at a wall maybe some of it would stick. I'm here to talk about Unit testing, there are other forms of testing but this is something that applies to any language and it will change your mindset completely.
Let's imagine we want to write a shopping cart thingy for our (person with the money) client. We have been given a list of stuffs that the client needs our code todo:
- it should add products to cart
- it should increase quantity of the product in cart
- it should decrease quantity of the product in cart
- it should reset the quantity of the products in cart
- it should clear the cart
Okay so its looking like maybe 5 functions, and maybe some how we manage state (remembering what changed in qty). Okay even my scientific calculator can do something so simple. Im sure we could code that up, sure enough we just did, it was a few days and nights of work, don't you remember?
Well the client is happy with your work anyway good job!
But clients do what clients do best, they ask for more features, what if the client comes along and says "I want coupon voucher discount guids my customers can enter to save money but not much money".. 💰 can we be sure that if we add that functionality we won't break something else? It seems unlikely that quantity effects price so the code is unrelated, it shouldn't break should it?.. Should it!?
Truthfully nobody knows, and without tests we need to resort to manually going through the cart journey atleast 30 times trying all sorts of combinations trying to break our code, we then say we are satisfied enough and move on. It's not good enough is it? 🤠
So what do we mean by tests?
NASA built a couple of rockets but they didn't stick some people in there and hope for the best. The engines where strapped to a massive rig and probably fired at-least 30 times in a big test chamber (excuse my loosely encyclopedic description of events). Black Masa on the other hand, well they didn't run unit tests they just sort of said okay Gordon, push the sample into the beam, look what happened there!
"So what? we have to take our code cut it up into component parts, strap it to a massive rig and see if it works?" Yup, exactly, it's called exercising your code, your code is tested in a rigged environment that allows us to control the precise inputs and hopefully get the expected outputs.
You can do TDD test driven development which basically means write some tests before you write some code. Remember our list of requirements? Well they actually make perfect tests.
Let's test something
I'm not talking about specific languages here but let's take one of our requirements above and write a test in a couple of different languages, bear 🐻 in mind some languages actually have tests built in.
Regardless of the language, there are some conversation around testing that help you write consistent tests, I like AAA which specifies how to arrange your code in a test.
- Arrange (setup some fake data to test with)
- Act (call the function you are testing with the fake data passed in)
- Assert (check the result of the function is as expected)
I also like to prefix my test descriptions with 'it should' where possible.
// JavaScript framework Jest in a checkout.test.js file
test('it should increase cart quantity', () => {
// A
let fakeInitialQty = 5;
// AA
checkout.addQty("productFishCake", fakeInitialQty, 1);
// AAA
expect(fakeInitialQty === 6).toBe(true);
});
Now let's do the same in Rust
// Rust has built in tests and no framework
#[cfg(test)]
mod tests {
#[test]
fn it_should_incr_crt_qty() {
// A
let mut initialQty = 5;
// AA + AAA
assert_equal!(addQty("productFishCake", initialQty, 1), 6);
}
}
I hope the above answers the question, what should I test and which language. Test any function that potentially has a different result, not getters, not static properties, just functions, test as much as you can, if your code cannot be split then it isn't modular and should be refactored and if you ever hear anyone say this code can't be tested, call thier bluff because if there's a will there's a way.
Anyway the tests we just wrote have ran. Nice, looks like it passed, my terminal just gave me a bunch of green PASS text, I could get used to this!
We now know with certainty that our code is possibly probably working as long as the test was correctly composed. Now when we refactor anything we should run tests again, if something fails we can go back to the implementation code and fix the result. If somebody breaks your code you will know about it and they should be referred to your tests because tests are documentation as well! I could go to any library without documentation and get an idea of what the intention is and how we use this code. If we see no tests, just don't use the library, it's a sure fire way to say that this library is not ready for production.
Conclusion
Say goodbye to wasted hours of manual self verification, and paranoia. Your code all works or it doesn't. There are many other types of test, unit testing is just the theory, but in practice a user may still see a bug on the website, cli, game, smart fridge, this is where acceptance test come in, it's a remote control browser or terminal or whatever consumers see, it's a subject for another time. I hope this post helps you, do not be afraid, learn testing and never look back it's essential for commercial development.
Top comments (3)
I've never seen the meaning of doing tests until I got a job where Tests was crucial to save effort and identifying bugs, now I always do unit tests with AAA convention and cannot live without them
Yeah I had a similar point of view pre joining a large company, but honestly it was because I didn't understand it at all. I tried to remember that time and write to myself in a way I wish I had been explained by senior devs who take knowledge for granted.
It's worth mentioning the code above doesn't actually work 😅, it's just for simplicity.