This Is Why You Suck At Unit Testing

Поделиться
HTML-код
  • Опубликовано: 14 ноя 2024

Комментарии • 34

  • @ezekieloruven
    @ezekieloruven 11 месяцев назад +3

    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.

    • @CodyEngelCodes
      @CodyEngelCodes  11 месяцев назад

      Yesssss! I love this observation.

  • @CrookedCCez
    @CrookedCCez Год назад +6

    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

  • @GeoffNelson1
    @GeoffNelson1 10 месяцев назад +1

    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 RUclips with coding. Good code that makes me think differently about how I implement/approach problems. :)

  • @calebkrauter4027
    @calebkrauter4027 Год назад

    Great video! Thank you for sharing.

  • @SaifurRahman92
    @SaifurRahman92 Год назад +4

    How should we test functions that return void?

    • @CodyEngelCodes
      @CodyEngelCodes  Год назад +8

      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.

    • @nicoqueijo
      @nicoqueijo Год назад

      @@CodyEngelCodes Ah looks like this might answer the comment I just wrote 😅

    • @CallousCoder
      @CallousCoder Год назад +2

      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.

    • @khatdubell
      @khatdubell Год назад +2

      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.

    • @CallousCoder
      @CallousCoder Год назад +1

      @@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.

  • @khatdubell
    @khatdubell Год назад +1

    Writing code without tests is a foreign concept to me at this point.

    • @CallousCoder
      @CallousCoder Год назад +1

      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 😄

    • @etorty_dev
      @etorty_dev Год назад +1

      The mocks won't be useless if they help test your assumptions of expected behavior and help your code robust.

    • @CallousCoder
      @CallousCoder Год назад +1

      @@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.

  • @real-cid-chan
    @real-cid-chan Год назад

    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.

  • @saritsotangkur2438
    @saritsotangkur2438 Год назад +2

    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.

  • @armanmasangkay6513
    @armanmasangkay6513 Год назад

    Nice video. I'm curious, do you write test first or test after in your day job?

    • @CodyEngelCodes
      @CodyEngelCodes  Год назад +1

      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.

  • @bjk837
    @bjk837 4 месяца назад

    How can you mock in a way that won’t break when you change internal implementation?

    • @dihakirah
      @dihakirah 2 месяца назад

      Just avoid mocking everything

  • @nicoqueijo
    @nicoqueijo Год назад

    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?

    • @CodyEngelCodes
      @CodyEngelCodes  Год назад +1

      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.