Hacker Read top | best | new | newcomments | leaders | about | bookmarklet login

This is the wrong way to think about it, though. A function encapsulates some behaviour, regardless of how short or long it is. You don't test each line (directly); you test each function's mode of operation. So a function with one if statement in it potentially needs two (happy path) tests.


sort by: page size:

No. It's the same. If you have a function with a large if statement in it, you'd want to test all conditions within that if statement to make sure it was working, right?

You'd do the exact same thing if you were testing a series of pattern-matched functions. The tests would be unchanged either way.


You still have to test all the 80 lines if they're broken down into multiple functions, so it's something that you have to evaluate on a case-by-case basis.

It might even make it harder to test: if you break a function wrong, then you might end up with a group of functions that only work together anyway.

For example: when you break a big function into 3 smaller ones. If the first acquires a resource (transaction, file) and the third releases it, then it might be simpler to test the whole group rather than each one separately.


If the function was truly linear having a long function wouldn't be so bad. But it actually isn't, the example contains multiple branches!

Will people bother testing all of them? Or will they write a single test, pass in a pizza and just glance at it actually working? My guess is the latter, as testing multiple branches from outside is often tedious, vs testing smaller specialized functions.


Do you test every line of code you write separately? Probably not. You test a function that has 5 lines of code.

Same for anonymous functions. You test the functions that use them and that's usually enough. If not, then that is a good indicator of separating them out into named functions.


However, if you have a 1000+ line function that you split into small functions you can pretty easily write a few unit tests per function to see which, if any, of those chunks have problems and then need to be fixed. It's pretty much impossible to write unit tests that can sensibly test a non-trivial 1000+ line function. You might get away with it if it's doing something very straightforward but I wouldn't be very confident in it.

Writing well designed functions that do one thing enables good testing.

I've felt testing can be reduced to writing those functions twice and hoping at least one is correct. There has to be a better way.

Breaking an 80 line function into to 8x 10 line functions does not necessarily make it easier to test. Most of the time it just adds unit testing busy work, for no clear benefit. This becomes more clear if you imagine you wanted to test every possible input. Splitting the function in 8ths introduces roughly 8x the work, if each new function has the same number of possible input states. The math is more complicated in the general case, so you have to evaluate it on a case-by-case basis. Also, if you're trying to isolate a known bug, it might be beneficial to split the function and test each part in isolation.

They have a check clause which is detached from the functions themselves. This is where they would want you to do tests with multiple functions interacting.

They may be easier in some cases to reason about - which is great until you start writing tests.

Once you start writing tests you find that the size of test functions will reflect the size of the function you're testing. The test functions start to have huge set-ups in order to test a small bit of logic, and you end up spending a lot of time fixing tests every time you make a change to that function.

This is why it's often better to find ways to make functions smaller.

There is a difference however of people refactoring code into smaller classes / functions vs what I'd call "hiding" code in classes / functions. If all someone has done is broken a large function into a chain of functions then of course that is not good.

When people refer to small functions / classes they refer to breaking up the concepts that a class / function represents into smaller concepts that build upon one another. This also increases the reusability of code.

So in short - I agree, longer pieces of code can be better for reasoning about, but I disagree that that necessarily makes them better.


It's also about the compiler guaranteeing to enumerate every code path in and out of every function. A human writing tests is limited by what they think will happen, but the compiler knows.

A lot of those if-/case-blocks are precisely where I'd put functions :)

If you changed a bunch of those to separate, pure (i.e. side-effect-free) functions it would if nothing else make unit testing a breeze, and then you'd be free to fix bugs in the logic without fear. As it is, if I had a bug in that huge function I'd be really worried about breaking some edge-condition or implied-state 500 lines up etc.


Breaking routines into functions makes testing possible. If it's really only called once, the compiler can inline it for you.

In any other field, specifying a function by its value at a handful of points would be a bad joke.

If you don't know what you want your code to do then tests will just make it harder to experiment, and if you do know then there's no harm in writing them after the fact.


I'm not sure I follow... Can you provide an example? (junior dev here)

If I understand some of it correctly, I was contemplating this when I started writing functions for "single functional concepts" like, "check for X; return true or false", then called each of those functions sequentially in a single "run" function. Is that what you mean?

I found that approach much easier to test the functions and catch bugs, but your comment seems to go against that.


For a function with 2,000 lines of code, we have to be honest with ourselves and accept that testing will never be sufficient; if there's 2,000 lines of code, you can bet good money that there's global state manipulation as well.

Making the assumption that anybody is capable of sufficiently testing such functions and subsequently re-writing them will only introduce new bugs and old regressions.

Seems harsh, but I've had to work with a few such monstrosities. Global state galore.


He missed at least one: functions should be "small" and broken up for "testability."

No, functions should perform one task and perform it well. Not every conditional test inside the function needs to be its own function and have its own unit test. Sometimes a function that does just one thing and one thing well has more than one step to do that thing, and those steps don't necessarily belong in their own functions. Testability and composability are important, but that has to be balanced against locality of reference and context. When I see a colleague write in a code review that pieces of a function should be factored out of a larger function "just in case they'll be reused" I step on it hard. This is related to "over generalization" but not exactly the same.


Small functions (less dependencies) are easier to test

Maybe you can explain it to them this way - if they know what a function is. If you have a function that does something, foo, and it takes one boolean parameter, that's two test cases. If you add a second boolean parameter, now you have at least four total test cases. And so on.
next

Legal | privacy