From: Peter Duniho on
Joshua Cranmer wrote:
> [...]
> Instead, the simplest thing to do is to write enough tests such that all
> execution paths can be covered. Basically, find a few examples of common
> cases and use every corner case you can think of--null parameters,
> 0-length arrays, etc.

Noting, however, that those are two different goals. Many bugs relate
to execution paths that don't even exist, and in fact those are often
also related to corner/boundary cases.

Test code should definitely exercise all code execution paths. But it
needs to go beyond that in terms of coverage criteria; the test code
should in fact be designed to cover the range of possible inputs, not
the range of inputs anticipated by the developer.

Pete
From: Joshua Cranmer on
On 07/31/2010 12:51 PM, Stefan Ram wrote:
> So you can prove the operator �&� for booleans by testing
> false&false, false&true, true&false, and true&true.

Well, this works if you have the implicit assumption that there is no
reliance on other state. I suppose you could say that the test can turn
into a proof of correctness if you know that it covers all of the
possible cases for the implementation.

> However, one can not prove Math.pow(double,double) in
> this way. (Think of the Pentium bug.)

If you know the algorithm, you can reduce it to categories and then
prove all categories correct. If you change the algorithm, though, the
test is no longer necessarily complete test coverage.

Actually, I have had to struggle with finding representative tests of a
program when you have no knowledge of how it is implemented (i.e.,
writing an autograder). That was when I discovered that most people
don't know how to thoroughly test their own code to find problems such
as a really common one brought about by implicit type conversion.

--
Beware of bugs in the above code; I have only proved it correct, not
tried it. -- Donald E. Knuth
From: Alan Gutierrez on
Stefan Ram wrote:
> Joshua Cranmer <Pidgeot18(a)verizon.invalid> writes:
>> To quote Dijkstra, "Testing cannot prove the absence of bugs, they can
>> only prove the presence of bugs."

> Well, actually, a malign programmer could have implemented
> �bool&bool� to give false results only around midnight or
> only between 2010-07-31T18:47:24+02:00 and
> 2010-07-31T18:47:26+02:00 or only if there is a certain file
> in the current directory or only if the source file contains
> a certain comment. So, even testing once for false&false,
> false&true, true&false, and true&true does not prove the
> correct implementation of �bool&bool� for all environments.

I believe I'd make an exception for Byzantine fault tolerance.

Does that help define the concept? I'd like to be able to argue that my
program is at least as trustworthy as the platform on which it runs.

--
Alan Gutierrez - alan(a)blogometer.com - http://twitter.com/bigeasy
From: Alan Gutierrez on
Joshua Cranmer wrote:
> On 07/31/2010 12:51 PM, Stefan Ram wrote:
>
>> However, one can not prove Math.pow(double,double) in
>> this way. (Think of the Pentium bug.)
>
> If you know the algorithm, you can reduce it to categories and then
> prove all categories correct. If you change the algorithm, though, the
> test is no longer necessarily complete test coverage.

> Actually, I have had to struggle with finding representative tests of a
> program when you have no knowledge of how it is implemented (i.e.,
> writing an autograder).

I'm not sure how you'd go about testing without source code and coverage
tools. I can imagine that you could investigate an implementation, using
a test framework, but I wouldn't have too much confidence in tests that
I wrote solely against an interface.

> That was when I discovered that most people
> don't know how to thoroughly test their own code to find problems such
> as a really common one brought about by implicit type conversion.

Which would that be? Curious. I'm probably one of the people that
doesn't know how to test for it.

--
Alan Gutierrez - alan(a)blogometer.com - http://twitter.com/bigeasy
From: Alan Gutierrez on
Tom Anderson wrote:
> On Thu, 29 Jul 2010, Lew wrote:
>
>> On 07/29/2010 12:39 AM, Alan Gutierrez wrote:
>>
>>> I'm working on implementing a B+tree and I'd like to be a little more
>>> rigorous with testing than I have in the past. What I'd like to do is
>>> write tests that prove the validity of the algorithm.
>>>
>>> The algorithm works, mind you, but I don't feel like I can prove it. I
>>> want than a bug stomping test suite. I want tests that capture the
>>> reasoning behind the algorithm. Ideally I could point you to these tests
>>> and you could see a proof. You could find fault in the reasoning or else
>>> gain confidence in the implementation.
>>
>> Tests don't give a formal proof. They just show that certain cases
>> produce expected results.
>
> True. I think what Alan is getting at is more 'testing as argument' than
> 'testing as proof'. For instance, if i was going to write such tests for
> an implementation of quicksort, i might write tests like:
>
> // i have an isTrivialCase to weed out arrays that don't need sorting
> isTrivialCaseReturnsTrueForLengthZeroArray
> isTrivialCaseReturnsTrueForLengthOneArray
> isTrivialCaseReturnsFalseForLongerArrays // test 2, 3, 100

> At each step, i test one of the invariants which are the building blocks
> of the proof of correctness. The tests aren't a proof, but they help
> illustrate the proof - and check that my implementation conforms to the it.

Yes. I mean testing as argument, with the intent of persuading adopters
of the code that code is of a certain quality, as well as persuading myself.

I don't mean to get into a discussion about the Halting problem, nor do
I mean to assert through testing that the software will never fail under
any condition. I did however run across the field of formal verification
in my research on this topic.

CMU - Specification and Verification Center

http://www.cs.cmu.edu/~svc/

Verifying an Operating System Kernel

http://ertos.nicta.com.au/publications/papers/Klein_NEH_07.pdf

That would be overreach for me at this point.

Indeed, I'd like to structure testing so that another programmer can see
my reasoning, step though the logic for an aspect of the implementation,
feel confident that it is correct or else tell me what I might be
missing. If someone can understand that I am trying to show that
`partitionLeavesTheArrayPartitionedAroundThePivot` they are going to be
in a better position to find the edge case and show me that I'm wrong.

The JUnit FAQ has this algorithm:

becomeTimidAndTestEverything
while writingTheSameThingOverAndOverAgain
becomeMoreAggressive
writeFewerTests
writeTestsForMoreInterestingCases
if getBurnedByStupidDefect
feelStupid
becomeTimidAndTestEverything
end
end

I don't see it as aggressive versus timid. I have no problem getting the
100% line and branch coverage, because I favor testing, and any best
practice that makes something impossible to test is rejected in favor of
that which is testable.

I'd like to see it as persuasive and communicative.

So I'm wondering how to structure testing to convey reasoning and
instill confidence. Seeing a bunch of unit tests named ticket81930(),
ticket81931(), ticket81932() doesn't really instill confidence because
you just see bug squashing, but no reasoning.

Anyway, thanks to Tom for the detailed response. Maybe that is all there
is to it, create a well documented JUnit `TestCase` with comments and
meaningful method names.

--
Alan Gutierrez - alan(a)blogometer.com - http://twitter.com/bigeasy