Video nenà dostupné.
OmlouvĂĄme se.
This Is Why You Suck At Unit Testing
VloĆŸit
- Äas pĆidĂĄn 1. 12. 2022
- In this video, I'm going to show you 4 common mistakes that software engineers make when it comes to unit testing. By understanding and avoiding these mistakes, you'll be on the road to success in your programming career!
Unit testing is one of the most important aspects of software development. It allows you to catch mistakes before they make it into your production code, and it can save you a lot of time and hassle down the road. In this video, I'm going to share with you some of the most common unit testing mistakes that software engineers make. By avoiding these mistakes, you'll be on your way to successful unit testing!
đ¶ââïž FOLLOW ME đ¶ââïž
Join Our Discord - / discord
Twitter - / codyengeltweets
TikTok - / codyengeltalks
Medium - / membership
Subscribe To My Newsletter - www.engel.dev/
đĄGet 20% Off Of Brilliant - brilliant.sjv....
đ„ My CZcams Gear - kit.co/CodyEng...
đ„ My Desk Gear - kit.co/CodyEng...
đ” My Background Audio - www.epidemicso...
*The above links are affiliate links.
#softwareengineer #programming #vlog
0:27 - testing the implementation and not the behaviour
2:00 - your tests never fail
3:20 - your tests aren't clear
6:25 - not running your tests as part of a build process
Doing gods work đ
Good takes, would love to see solid code examples that aren't contrived "shape" classes or reading a character string and making sure it isn't a number. That's what I'm always looking for on CZcams with coding. Good code that makes me think differently about how I implement/approach problems. :)
How can you mock in a way that wonât break when you change internal implementation?
The biggest observation that got me to use TDD mostly is the realization that TDD is always what you are doing, just that if you don't have a formal test, your "test" is the object's use in your code itself. So if you find you need another object, there WILL be tests and an expected interface, just those tests are now embedded in your code, and you can only know it works with your specific code and specific situation, not in all conditions you will inevitably end up changing your code into.
Separated unit tests ensure your object works as expected in all conditions it should face, and having those tests make it so you can confidently optimize or enhance functionality without losing our breaking old functionality.
Yesssss! I love this observation.
Great video! Thank you for sharing.
Thanks for watching!
How should we test functions that return void?
You should rewrite those functions so they don't return void. If that's not possible then you'd want to validate whatever side effect the `void` function causes actually happens as expected.
@@CodyEngelCodes Ah looks like this might answer the comment I just wrote đ
In programming thereâs a distinction between functions and procedures. Some languages (like pascal) actually have those keywords.
A void (aka a procedure) just executed a set of commands and changes the state of the system.
This per definition means itâs not a single unit of work and thus not a unit test. Itâs potentially a candidate for a integration- or end to end test. This is why Iâm more in favor of integration and end2end tests as it will implicit with also test your small functions.
A function is not supposed to change the state of your system. It merely performs logic on variables and therefor doesnât change the state of the system.
I like that very strict difference, I donât know why that separation became less and less clear as program languages got younger - well I do know just donât really approve.
As I tend to really hold firm to these two principles: A void changes my system a function merely changes program data. A void used functions to achieve that system change, a function only contributes the variables for that.
So for example updating a file where youâll remove say
or Unix and
for windows.
You will have a function returning either
or
depending on the OS.
You pass that in as a variable to a void (procedure) that opens the file reads the content and does the replacement and reads it back.
Thatâs the way to separate the two.
Now the astute among you will have seen immediately a little gap in that hard separation. What if you want to know if the procedure was successful?
Or that opening that file was successful. You can (and should) check if the file exists and the rights are okay (two functions as they donât change the state) but then thereâs still runtime errors for example a disk failure.
You could throw an error which is technically is not a return value, but ironically there was no try catch in pascal.
And this is where the lines start to blur between the two.
In C I tend to pass in a reference to a status variable and have that updated and checked. So itâs still a void but I have the status either in an int that I passed in or a pointer to status structure, which I update each step of the process, so that I also know and can report where a process (procedure failed).
This very strict distinction I only hold dear on embedded systems as you have little to no means to actually check the state of the system and you simple have to trust that when you change an output pin or piece of hardware that it actually did that. With operating systems at the abstractions of file systems etc you can test this and I do use a return value in a âprocedureâ from time to time.
But itâs a great practice to quickly see what changes the state of your system/program and what does not. But as with everything in CS there are edge cases that blur the edges.
Just a side note.
Don't be afraid to write functions that return multiple values.
Whatever you have to do to avoid "out parameters" (most often seen with functions that return void or bool) is _usually_ justified.
@@khatdubell Iâm not a fan of returning more than one value. It is a better practice to then return a structure with the values in that structure. This also is far more portable to other languages, because most (sane) languages do not allow to return multiples values. And when you do it truly properly, aka in the C way. You allocate the structure outside of the function call and pass it in my reference and have the function update the values. But you can also return the same pointer. And of course free the pointer after your done. This is the reason to allocate it outside of the function. Because then you know you created it and you need to free it. The function canât free it because then itâs out of scope. Iâd itâs an object then it could be freed when the destructor is called but nobody knows if it is when itâs a library.
And this is why I advocate first learning C and/or assembly because then you know what happens with memory management and you can make better judgement when using interpreted (ugghhhhhn)
đ€ź) languages.
What about a scenario where the function that's being tested doesn't produce an output but does a side effect. Is it okay to use "verifys" there to ensure that when that function was called the code took a certain path in the function's logic?
You can, but if it's a side effect that's in the background it's important to ask why you're validating that it happens. If it's a side effect with no observable behavior then it may not be that important for the system to function and could just be removed. Or if it's only logging a message then it's good to ask if it's worth validating the log happened, sometimes it is important (say if it's logging an error or warning) other times it's not.
Nice video. I'm curious, do you write test first or test after in your day job?
It depends but I almost always write the test first, then the code to satisfy the test, and then I repeat that until the task is done.
For projects that are more in crunch mode I will forego writing tests but that code is really intended to only exist for a short period of time until we can go back and update it to satisfy technical requirements. I write the bare minimum required to get things to work so that ideally discourages folks from adding onto the ball of mud.
Mocking is not bad. If you already have good unit tests for some functions, you will sometimes want to assert that those functions were called with certain parameters in the functions that call them. Otherwise youâd get a combinatorial explosion of cases you should test.
Writing code without tests is a foreign concept to me at this point.
Try developing software that controls external resources like controlling cloud services or even more difficult, hardware like microcontrollers controlling AD/DA converters or i2c devices, motor controllers đYou canât write useful tests for that. Your mocks will be a âassumptionâ and thus useless. You can then only do end to end tests. But you can only write those when youâve written the implementation đ
The mocks won't be useless if they help test your assumptions of expected behavior and help your code robust.
@@etorty_dev assumption is the mother of all f-ups. You should develop against a test instance. Thatâs the only way to cover all basis. The devil is always in the details.
Interesting fact I have observed:
If you test your initial version of your function, it is guaranteed to not work (unless it is a trivial function, in which case, testing that function might be overkill). So test that initial version, (which fails), and then start fixing it.
P.S. by "initial" version of the function before you ever ran it ever.
Yep that's fantastic advice!