Say we have an application covered by 1000 tests. We fast forward a few years and (amuse this crazy notion) the application is decommissioned. We look and see that 500 tests never failed; those 500 always passed. Did we waste dev time by writing those tests? It's an interesting question to think about.
Coverage is not strongly correlated with test suite effectiveness
161–170 of 178 posts
Re: Coverage is not strongly correlated with test suite effectiveness
#162Earlier quoted context omitted.
I wouldn't even write this way because the else confuses what's happening. void fn(int a, int b) { string ret = "not"; if (a == b) ret = "equal"; printf(ret); } *I understand, this is not a good optimization in every language.
How does the 'else' confuse what is happening?
This is a quirk of human text comprehension. In a small program, it seems arbitrary because you can see it all at once. Make the branches do more than simply printf and you can see why you want to frontload the else, rather than pick through the if/else to see what always happens.
Re: Coverage is not strongly correlated with test suite effectiveness
#163Earlier quoted context omitted.
I wouldn't even write this way because the else confuses what's happening. void fn(int a, int b) { string ret = "not"; if (a == b) ret = "equal"; printf(ret); } *I understand, this is not a good optimization in every language.
IMHO the original version without the mutable local variable was much more obvious. In any language. Though I would also accept: void fn(int const a, int const b) { printf(a == b ? "equal" : "not"); } to avoid duplicating the call to printf(). If this were Rust I might suggest: fn theFn(a: isize, b: isize) { let ret; if a == b { ret = "equal"; } else { ret = "not"; } println!("{}", ret); } but only because Rust, unli…
This is not about obviousness. You can write the same expression lots of different ways, which are all obvious. This is still a question about testable paths and code hygiene.
Nesting the same function call in both branches of if/else unintentionally obscures that it's not a conditional printf call.
If you're going to write:
printf(a == b ? "equal" : "not");
you don't need the function at all, completely missing the point.
Your second example is definitely better in Rust.
Re: Coverage is not strongly correlated with test suite effectiveness
#164Earlier quoted context omitted.
From the entry point, stub a function to throw an exception. You'll reach it. Unit testing isn't about finding every path. Nor is it about writing unit tests for every possible combination of values in a program. A java function that uses an int does not need to test -2147483648 to 2147483647 as inputs. That's not helpful.
That’s an interesting example, that’s exactly where you’re likely to find bugs - at boundary values. You should test those more than you should test the number 5. This is exactly why unit testing gives a false sense of security. You’ve done a lot, you’ve written all kinds of tests - but at the end of the day, you don’t get a proportionate amount of confidence about the code because there are infinite more cases that…
Re: Coverage is not strongly correlated with test suite effectiveness
#165Earlier quoted context omitted.
> We have the tools for understanding and reasoning about infinitely large structures, programmers just refuse to use them, and even deride them. Could you point out which tools you're talking about?
Well, by this I mean math (and mathematical logic). Math is the tool for reasoning about possibly infinite concepts, i.e. you can make a statement about infinite sets and still know if it’s true or not. We aren’t limited to what we can see and touch, which is good because any non-trivial software application is so large that it could never be drawn out like a building blueprint. It can only be described and reasoned…
I am probably one of the developers who would gladly use anything like that, but from my brief experiments and knowledge, they are very, very far from becoming usable in a general setting, so no, I don't refuse to use the tools, the tools are just not good enough to justify the high cost of using them yet.
Re: Coverage is not strongly correlated with test suite effectiveness
#166Earlier quoted context omitted.
Swings and roundabouts: what you lose on writing tests you gain on not bothering to implement things you don't need. If the test passes, you stop. Without the tests to guide you it's very easy to waste time over-engineering, even if what you're building is well-structured.
I often write code without implementing anything, just the "surface API". That's when I find whether things will work or not. Tests are a hindrance to that. Once I figure out the design, then I will test all that I think is important. > what you lose on writing tests you gain on not bothering to implement things you don't need. I am not sure where to start... I've written so much code, applications, libraries, algori…
There's an entire school of TDD which works this way. That's not incompatible at all.
> with TDD, I would've spent hours trying to get something working that later I would find, by exploration, that I didn't need at all.
If you're writing tests for something you don't need, the problem isn't with TDD as I understand it.
Re: Coverage is not strongly correlated with test suite effectiveness
#167Earlier quoted context omitted.
IMHO the original version without the mutable local variable was much more obvious. In any language. Though I would also accept: void fn(int const a, int const b) { printf(a == b ? "equal" : "not"); } to avoid duplicating the call to printf(). If this were Rust I might suggest: fn theFn(a: isize, b: isize) { let ret; if a == b { ret = "equal"; } else { ret = "not"; } println!("{}", ret); } but only because Rust, unli…
> IMHO the original version without the mutable local variable was much more obvious. In any language. This is not about obviousness. You can write the same expression lots of different ways, which are all obvious. This is still a question about testable paths and code hygiene. Nesting the same function call in both branches of if/else unintentionally obscures that it's not a conditional printf call. If you're going…
And the version with mutation masks the fact that the initial value assigned to `ret` may or may not be used, which seems like a very similar problem to me. Plus it involves mutation, which IMHO significantly increases the complexity of any code. Unfortunately C offers limited options here; as cryptic and controversial as it may be, the ternary operator is the only (non-hackish) way to make an expression which selects between two or more values based on a boolean condition without either the possibility of failing to assign a value or requiring a "default" value which at least some paths through the function won't use. (What if there is no reasonable default value and all the required branches are expensive to compute?)
> If you're going to write: … printf(a == b ? "equal" : "not"); … you don't need the function at all, completely missing the point.
The function still encapsulates the specific comparison and the way the result is reported—exactly like the other versions—so I wouldn't say it's completely useless despite containing only one line of code. This is, of course, merely a toy example. You could assign the string to a named const variable if you wanted to make it slightly more self-documenting, though I question whether that would make this particular code any more readable.
Re: Coverage is not strongly correlated with test suite effectiveness
#168Earlier quoted context omitted.
Well, by this I mean math (and mathematical logic). Math is the tool for reasoning about possibly infinite concepts, i.e. you can make a statement about infinite sets and still know if it’s true or not. We aren’t limited to what we can see and touch, which is good because any non-trivial software application is so large that it could never be drawn out like a building blueprint. It can only be described and reasoned…
I know about all the tools you've mentioned as I've been following the space from a distance, was just curious if you had a more "outside research" approach in mind when you claimed developers refuse to use the available "tools"... I haven't seen Coq, or even Idris or Ada+Spark, which arguably much closer-to-the-industry tools, being used for anything but very niche cases (like the case you mentioned about using TLA+…
You sound open to them, but dissatisfied with the tools themselves. I'm honestly in that camp too! We are not there yet in terms of being able to use this as the primary way that an entire application is developed. But, I believe that's the direction we should go in. You're right - we really need a tool that wins people over with the cost to value ratio. The promise of something far off in the future isn't enough for most people.
Re: Coverage is not strongly correlated with test suite effectiveness
#169Re: Coverage is not strongly correlated with test suite effectiveness
#170Earlier quoted context omitted.
How does the 'else' confuse what is happening?
The function will perform a printf, so nesting it in an if-else subconsciously implies it's optional. This is a quirk of human text comprehension. In a small program, it seems arbitrary because you can see it all at once. Make the branches do more than simply printf and you can see why you want to frontload the else, rather than pick through the if/else to see what always happens.
int pow2(int v, int pow)
{
int result;
if (pow >= 0)
result = v > -pow;
return result;
}
Looking at this, both sides of the if/else perform a multiply by 2^pow, you can't take the operation to possibly not happen.So I agree factoring common operations can be a good thing, but I think it's really a case by case basis and my code is fine.
I would also caution against assuming something may not happen elsewhere in a function just because you see it inside a conditional block. This happens frequently to various degrees and I've never encountered a style policy preventing it or any compiler or linter warnings for it.