BrandGhost

Here's Why YOU Should Write Those Unit Tests [C# dotnet Examples]

In this video, I'm going to show you why you should write unit tests for your C# code. I'll be sharing some dotnet examples focused on my result set cache implementation that you can use to help you understand the benefits of unit testing. Writing unit tests for your C# code can help you identify and fix errors early. Not only that, but unit testing can also help you improve your codebase by making it more robust and reliable. So if you're not writing unit tests yet, then you should reconsider!...
View Transcript
so I'm gonna be honest with you I'm really bad at mapping things between different ranges and for me this comes up all of the time in software engineering so one example is if I'm trying to create even 2D video games and I have to think about mapping things between different coordinate spaces I just can't do it for some reason it doesn't click in my brain and I have to resort to getting on pen and paper and drawing it all out and even then translating those ideas into code they just never seem to click this problem even exists for me when it's not two-dimensional so if I have to map between different arrays of data and different ranges I struggle with that all the time in fact I spent about eight years of my career writing digital forensic software and we had to do this non-stop we had to go look at unstructured data take ranges of that and go structure it to make sense of it so why am I telling you this aside from just being vulnerable and sharing that I have some weaknesses as well well this all comes back to testing and when I talk about testing I refer to testing as one of the best tools we have to build confidence when we're making changes to code and for me when I'm really struggling with things like being able to map ranges of data between different coordinate spaces or even just between arrays when I finally get my algorithm working this is something that I'm making sure that I have a ton of test coverage on because if I need to iterate on that at all I want to make sure that different parts of that algorithm aren't totally falling apart additionally because of the experience that I have I know I struggle with mapping ranges so for me having more tests means more confidence in this area and that way when I finally have it working I can feel safe to move on to something else and not have this brittle thing that I was worrying about in the first place so I wanted to walk through some code like this with you today because I actually had some recent code working with caches or I had to map ranges of data and it was a perfect opportunity to go write a bunch of unit tests on it so before I jump over to visual studio I just want you to pause for a second and think about if there are things that you always seem to struggle with when you're programming for me it's mapping ranges of things for you it might be something else so think about that and then as we're walking through this code and I'm talking about my testing I want you to think back about the thing that you struggle with and see how you can apply your tests to these situations and with that said let's jump over to visual studio alright so in my most recent videos I've been talking about caching a lot and recently I posted a video about this result set cache which I will link up above so you'll see a little thing in the top that you can go click on and watch if you haven't seen that already just briefly the idea behind these result sets and the caching is that if I have a page of data that I'm pulling back from a database based on a filter I want to make sure that I can organize these in a cache so that I can retrieve them later and I need to be able to merge the pages together and that's going to bring us back to this concept that for me I really struggle with because I'm going to have to map ranges of data to try and merge these Pages that's something I really struggle with in addition to merging we also have a method on this result set cache that allows us to try and get a page out now if we already have a bunch of pages inside of this result set we can actually ask for an offset and a page size to get a partial page or some page that might overlap several pages to together again that brings us back to this range mapping thing that I totally suck at if we scroll down a little bit further we can see in this part of the method here we're actually looping through the pages and trying to find the pages that map to the range we're looking for and then we're going to be adding those IDs from the result set into the result that we're going to return to the caller the details of this aren't super important for what we're about to discuss but I just wanted to show you that this is part of the method that's giving me a ton of headaches when I think about how I need to implement this and so that I can have confidence that it's working as expected if I jump over to the result set cache class it's very similar in the sense that I have to do some range mapping and it really screws me up inside of this method to set async we're actually able to take in a result set page and this is where if I scroll down a little bit lower we're going to do some logic to actually insert a page and then try to merge the pages after in fact this algorithm can likely be highly optimized but I didn't know how to do it yet and because it was functioning and I had tests over top of it now I'm actually in a position where I have confidence in its performance and Its Behavior and I'm able to go iterate on that because the nice thing is currently it's working if I expand this code a little bit just so we can have a look at it this part that's going to insert a page is actually going to look to see where it can insert the page and then from there it either inserts it or it's going to add it to the end if it's not part of an existing range already again this logic probably could have looked for a page that it's going to line up with and try to merge it however I just wasn't sure how to go do that and I'm totally comfortable admitting that this is something that always really screws me up because I've seen it time and time again with different applications and it always comes back to haunt me the nice thing is I have a tool and it's called testing and I always end up applying it for me to get that confidence that I need if I scroll a little bit lower we can see this part of the logic that's going to end up doing the page merging and again we have to go work across different ranges and this Logic for me to go right I had to get pen and paper out and try to make sure that it made sense from here I want to go jump over to the test that I started to write starting with the result set test this is where I had a bunch of scenarios that I started with so it wasn't quite tdd and I just want to explain why it wasn't quite test driven development so for me when I start writing algorithms and it's something that I know that I'm going to be getting confused about I go rate to pen and paper so in this particular case when it came to trying to add pages in and then figure out how to merge them what I generally do is almost like tdd except it's with pseudocode and pictures so I'm not actually coding up the tests ahead of time I'm actually just trying to walk through couple of scenarios to come up with the algorithm that I think is going to work and I don't start with what I think is going to be the most optimal I start with something that's going to function if I have it working in the first place that might actually be good enough it might take a couple of iterations and I can see some obvious optimizations but sometimes I just skip that until I go to Benchmark it and then if I realize there's some problems I will come back and revisit it but when I come back to revisit it I already have tests in place so I'm in a good position to be able to refactor it and have confidence so it's not quite tdd because I don't go code up all of the tests have them breaking and then say great let me go start writing the code and make them pass one by one instead what I do is I pick a scenario or two and then I start making sure that it checks out on paper so I start walking through my design and then going okay that doesn't work let me change it and I get one or two scenarios working and then from there I actually go code the algorithm first I don't code the test first so I still kind of start with the algorithm after I'm on pen and paper and then from there I might go cool it's compiling I at least have a couple of scenarios I think will work then I add the tests for me this is really useful because it lets me check out that I've at least got part of the algorithm working as I expect and I have some regression against that from here this is where it starts to turn into like tdd for me so now that I have some part of the algorithm in place and a couple of tests that are passing from there I can start going well what about this scenario what about that scenario and then I can see usually pretty quickly that I did not think of all these edge cases and my algorithm starts to fall apart but that's cool because I know those base cases still pass and then I can start iterating by adding additional tests and making sure they pass so just to quickly reiterate it's not quite tdd because I go to pen and paper first get that kind of checking out like doing some mental math and walking through and drawing diagrams making sure the pseudo code kind of checks out then I code part of the algorithm and then I code a couple of tests to make sure they pass from that point on it starts to turn into a little bit of tdd and I just also wanted to mention that I don't apply this to everything a lot of the time I have a high degree of confidence in a lot of the code I'm writing so for me I might go write different types of unit tests or functional tests depending on what I'm working with but this kind of TD like half tdd approach like pen and paper to tdd I don't do that with everything I do it with things that I have a low degree of confidence with and like I said in the beginning of this video it's almost always mapping ranges of data okay cool let's go back to visual studio and check out some of the tests so I have my result set test pulled up on the screen now and if I just collapse everything and we start going through them one by one just kind of looking at the titles of the test we can see how my thought process might have started to form up now these aren't necessarily written in a sequential order that I created them in however we can probably have a guess at how I started and then some of the more complicated scenarios that came up later for example this test rate here is to try and get a page and we're asking for one page where the target is exactly what we have inside of the result set this is probably one of the base cases that I worked with and this is something I figured this should work and it should be easy to code and the actual algorithm to make this happen is probably pretty simple again without going through all the details of how my result set actually works the idea would be that if I'm asking for a range and an offset I can actually just check to see if I have a single page that's the right size and if the offset was Zero then I know it's going to match so for me this is a really simple test case that I could go put together and probably one that I started with right in the beginning another simple case that I probably looked at right in the start was this one here that says one page that's the target is completely before the data that we have inside of the result set so if you look at this result set here that says from IDs five all the way up to nine the important part to note is that it's actually starting at an offset of five so that would mean that if we were pulling data from a database this is just an example that we would be asking for a page that started at five and then we pulled an extra five IDs from that point on so if we look at the page we're trying to get this says start at offset one and get three IDs so if you think about what I had just said because this page that we have actually starts at offset five five absolutely comes after one plus three right which is four so this entire page that we're asking for is totally before this page that we have inside of the result set and as a result this is a pretty simple one as well we're going to get false for the return value and the page that comes out it's going to be null there's going to be a very similar one which is the exact opposite case where we have a page that's actually we're targeting completely after all of the data that we have in the result set so this one we don't have to go look into but if you see my thought process I started with something that was exactly the same something completely before and something completely after at this point I probably had some of my algorithm coded up because I was able to walk through it on paper but this is where it starts to get a little bit more interesting with something like this one here where one page that's our Target is partially before the data that we have in the result set so if I go look at this setup here we're actually saying like the one that we looked at earlier we're starting at an offset of five and we have five items that are in our result set the page that we're asking for instead of being at offset one is actually at offset two and we're asking for four pieces of data so that means our page sizes for our offsets two and we're going to be overlapping by one that's in here now based on how my result set cache works if we don't have a full page to pull back we don't expect to get anything and that's why we have a false here and a null page coming back just like the other scenario in the future when I'm looking at different types of optimizations that I can use with my cache I might actually allow something like try get partial page and that would mean that this method still checks out to be the exact same well when we don't get a full page we actually say false but if we ask for a partial page and we used a very similar scenario like this one that's where I might say cool we actually were able to get one of the items coming back at this point in time I don't have anything that needs partial pages but in the future when I'm looking at optimizations like I mentioned I might go explore that I added a whole bunch of additional scenarios that I thought might be kind of interesting to go look at but something that I wanted to touch on is this optimization that I have for my result set cache when we actually have the full result set pulled into memory when we had the full results that pulled into memory if you recall I said I'm going to be merging pages together so a full result set will mean that I have only one page and it will be the entire range of data that we could possibly expect to get pulled back from a database query and this is where some of the cool optimizations come into play because I don't have to go iterating across different pages all that I have to do is look at the single page and actually just pull the data out of that so just to illustrate that I'm going to jump back into the result set here and if I scroll up a little bit further you can see that I have this if statement that says if full set and what we can do is actually see if we're asking for a page that is going to be within the full set and I actually have this check here that allows us to ask for a page size that's beyond right so this kind of seems counterintuitive to what I said earlier so if we were asking for a offset of zero and a page size that actually exceeded the total count we have I'm still going to return that page and this is something that came up after I designed my algorithm when I was running my code with the cache in place and the reason this came up is because most of the time I'm asking for page data for my database queries however there's a couple of situations where I just say int.max is the page size and the expectation that I had in as a caller was that if there's a full set of data just go give me that and I end up doing this because I know I have a couple of queries that are going to always return only like you know say a hundred to a thousand records and I do want all of them I don't know the exact size but I do know that I have a very small amount that can always fit into memory and I just want to be able to pull everything in so I ended up having to change this logic here and actually adding an additional test case that was not map exactly to this but actually allow it to exceed now here's a fun part in one of the last videos that I put out when I was explaining some of this code I actually had a fix me left in here that said I think that there's some more optimization we can do when we have a full set of data so if I scroll down a little bit lower instead of having to use an iterator and then go step over each of these Pages adding things into a list I said hold on a second we know that we have one single page now this page of data has IDs and the type that comes back is actually an I read only list however there's a lot of optimizations that exist in.net now that allow us to do faster operations on lists and arrays and because almost all of the time I'm only using these two things as collections behind the scenes I said why don't I try writing a couple of optimizations specifically for these types so generally you won't find me ever writing code that looks like this where I'm doing type checking and then some fancy Logic for it but this was something that I felt like because it's in a cache and it's supposed to be optimized for performance I might as well try to squeeze out what I can especially because there's a couple of things that we can use for collections here so what was cool about this was that I could go add this logic in here try it out and see if I broke anything for my tests and the reality is I broke almost everything by adding this stuff in when I was looking at the tests that we're trying to operate on a full set and why did that happen well it's because I really suck at mapping ranges of data I knew that was going to happen I knew that when I tried to go to approach this that I was going to mess it up and that's okay I felt confident that I could go try this out because I had tests in place over the whole method already and that meant if I was going to bust anything no problem I can go back and make sure that the tests are passing after and as I mentioned because I'm almost always using a razor list what I found was that one of these scenarios was totally busted and then the other one wasn't even executing so I believe I was using lists almost everywhere and almost never using arrays so I said cool let's make sure that the array path also passes so if I jump back to the test here you can see if I scroll a little bit lower here I do have full result set array I have full result set list I have full result set other and I put this one in here because after making sure that arrays and list pass I said wait a second that's going to mean that some of my other code is never going to execute so if I scroll back into this result set if you recall what I just said if I'm always using lists and arrays will basically never fall after this else if if we're going through here and that's going to mean that there's all this code that could go run after and it's going to take one day or I put in a different type behind this IDs property and that's going to break everything so I said okay let me go add a test and let me make sure it's not a list and not an array so if I jump back to the test and we go look at this one in particular I actually said for the result set page that I have in here I'm going to make it an immutable sorted set and an immutable sorted set is not a list and it's not an array so this was really cool because I could go add additional coverage to this other scenario that currently doesn't exist in my code base but I am going to have protection against going forward and for me adding these additional scenarios like this comes back to confidence I am not confident in this type of code that has to go mapping ranges so if one day I did have a different collection that was backing the IDS property that was going to mean that I'm probably going to have a really hard time debugging this stuff because it just never seems to click alright so in summary aside from me saying a hundred times over that I'm really bad at mapping ranges of data which is true I'm terrible at it it freaks me out every time I have to go do it I still do it aside from that I think the takeaway that I want you to have is that if you're thinking back to that thing that I asked you about in the beginning of this video which is what do you struggle with when you're programming what comes up all the time and you're like man I know I'm going to struggle with this if you think about my testing approach how can you apply that to the thing that seems to always trip you up so what I would suggest is for that thing that's always messing you up get pen and paper out draw pictures go to a whiteboard try getting your algorithm working try walking through it and making sense of it and you don't have to worry about it being perfect and you don't have to worry about it having the highest performance ever I just want you to get the base case working and I want you to feel comfortable with that just to start from there I'd recommend maybe giving it a shot trying to code up part of the algorithm and when you think that might be working go out a couple of tests for the base cases that you were working with when you're at the Whiteboard or with pen and paper now you're in a position where you can start to build on some of that Foundation you have a partially working algorithm and you have a couple of scenarios you can regression test again against now it's up to you to come up with these other scenarios and think through if your algorithm can handle those so this is kind of like where you might introduce tdd and I don't think tdd applies to everything even though some people swear by it but I think this is a really good situation that I like to use it so I'm hopeful that if you can follow this approach for the things that you seem to really struggle with you're going to have a way better time going forward because the next time you encounter this you'll say not so fast I'm not afraid of you anymore I have a tool and it's testing and I'm gonna use it so I hope you enjoyed this I hope you have a takeaway that you can go apply and thank you for watching we'll see you next time

Frequently Asked Questions

Why is testing important when dealing with complex algorithms?

Testing is crucial for me because it builds confidence when making changes to code, especially with complex algorithms that I might struggle with, like mapping ranges of data. Having a robust set of tests ensures that if I need to iterate on my algorithm, I can do so without worrying that other parts of the code will break.

How do you approach writing tests for algorithms you find challenging?

When I encounter algorithms that I find challenging, I start by using pen and paper to sketch out the logic and scenarios. Once I have a basic understanding and a working algorithm, I write tests for those base cases to ensure they pass. This gives me a foundation to build on and allows me to add more tests for edge cases as I refine the algorithm.

Do you always use test-driven development (TDD) for your coding projects?

I don't apply TDD to every project. I reserve this approach for areas where I have a low degree of confidence, like mapping ranges of data. In those cases, I might start with pseudocode and diagrams, then write some initial tests after I have a working algorithm. This hybrid approach helps me ensure that my code behaves as expected.

These FAQs were generated by AI from the video transcript.
An error has occurred. This application may no longer respond until reloaded. Reload