Wed, 27 Sep 2006
More on boolean expressions 
  My examples below use a simple rule for deciding what values
  of a boolean expression to test. I should probably describe it and
  justify it. 
 
  Given an expression with all ands like X1
    and X2 and ... and Xn, you use these
    test values:
 
  
      One case: all the Xi's are true.
    
  
      N cases. In each, all
      the Xi's are true except
      for one that's false. (A different one every time.)
      The way I think about it is that, for
      each Xi, there's an example that shows the
      whole expression is false exactly and only because of it. 
    
 
  So the table for (A and B and C) would be this:
 
  
    
            
	
	  | 
	    A and B and C
	   | 
	 
	
	  | 
	    A
	   | 
	  
	    B
	   | 
	  
	    C
	   | 
	  
	     
	   | 
	  
	    expected result
	   | 
	 
	  
	    t
	   | 
	  
	    t
	   | 
	  
	    t
	   | 
	  
	     
	   | 
	  
	    t
	   | 
	
	  
	    F
	   | 
	  
	    t
	   | 
	  
	    t
	   | 
	  
	     
	   | 
	  
	    F
	   | 
	
	  
	    t
	   | 
	  
	    F
	   | 
	  
	    t
	   | 
	  
	     
	   | 
	  
	    F
	   | 
	
	  
	    t
	   | 
	  
	    t
	   | 
	  
	    F
	   | 
	  
	     
	   | 
	  
	    F
	   | 
	
       
     | 
   
 
  The case for or-expressions is similar: just flip all
  the trues and falses:
 
  
    
            
	
	  | 
	    A or B or C
	   | 
	 
	
	  | 
	    A
	   | 
	  
	    B
	   | 
	  
	    C
	   | 
	  
	     
	   | 
	  
	    expected result
	   | 
	 
	  
	    f
	   | 
	  
	    f
	   | 
	  
	    f
	   | 
	  
	     
	   | 
	  
	    f
	   | 
	
	  
	    T
	   | 
	  
	    f
	   | 
	  
	    f
	   | 
	  
	     
	   | 
	  
	    T
	   | 
	
	  
	    f
	   | 
	  
	    T
	   | 
	  
	    f
	   | 
	  
	     
	   | 
	  
	    T
	   | 
	
	  
	    f
	   | 
	  
	    f
	   | 
	  
	    T
	   | 
	  
	     
	   | 
	  
	    T
	   | 
	
       
     | 
   
 
  The reasoning behind these rules is based on mutation
  testing, the name for a long thread of academic research on
  testing. The way I state it (which is different in an unimportant
  way from how it's usually put) is that mutation testing involves
  assuming that the code is incorrect in some definable way, then
  asking for a test suite that can distinguish the incorrect code you
  have from the correct code you should have.
 
  Now, for any given program, there are an infinite number of
  variants, so mutation testing depends on picking a
  definition-of-incorrectness that (a) lets you generate a reasonably
  small set of alternatives, but (b) gives you confidence that you've
  caught all the plausible errors. The usual approach is to
  assume one-token errors.
 
  For example, suppose you are given (A and B or C). Maybe
  it should be (A or B and C) or (A and not
      B or C) or (A and (B or C)).1
 
  One-token errors aren't the only ones you could make. For example,
  you might completely forget that D ought to be involved in the
  expression—it should be (A and B and C and
  D). That's
  a fault of
  omission, and mechanical techniques aren't good at
  them. Nevertheless, one-token errors seem to work pretty well for
  boolean expressions.
 
  Suppose you have the original (A and B or C) and a
  variant (not A and B or C). The test
  value (A=true,B=true,C=true) distinguishes the two,
  because the given expression yields true while the
  possibly-more-correct variant would yield false. So,
  when you run the original program and its variant2, that test case will produce
  one answer in the original and a different one in the variant. One
  of them's got to be wrong. If it's the original program, you've
  found a bug. If it's the variant, you know that variant cannot be
  the correct program (the original is not incorrect in that way). In
  the jargon, the mutant is killed. 
 
  Trying all the possible combinations of variable values will either
  find a one-token error or kill all the mutants. But you never have
  to try all of them. There will be some test inputs that don't add
  anything: any mutant they kill will be killed by some other test
  input. So you can construct a minimal set for any given expression. 
 
  If you look at the table below, you can see that the rule
  for and-expressions I gave above is justified; the
  cases I give kill all the mutants. (In the table, the first row is
  for the expression as given; each row below it is a mutant. The X's
  in a cell means that column's test case kills that mutant.) 
 
  
    
            
	
	  | 
	     
	   | 
	  
	    
	   | 
	  
	    
	   | 
	  
	    
	   | 
	  
	    
	   | 
	  
	    
	   | 
	  
	    
	   | 
	  
	    
	   | 
	  
	    
	   | 
	 
	
	  | 
	    A && B && C
	   | 
	  T | 
	  f  | 
	  f  | 
	  f  | 
	  f  | 
	  f  | 
	  f  | 
	  f  | 
	 
	
	  | 
	    !A && B && C
	   | 
	  f /  X | 
	  f /  | 
	  f /  | 
	  T /  X | 
	  f /  | 
	  f /  | 
	  f /  | 
	  f /  | 
	 
	
	  | 
	    A && !B && C
	   | 
	  f /  X | 
	  f /  | 
	  T /  X | 
	  f /  | 
	  f /  | 
	  f /  | 
	  f /  | 
	  f /  | 
	 
	
	  | 
	    A && B && !C
	   | 
	  f /  X | 
	  T /  X | 
	  f /  | 
	  f /  | 
	  f /  | 
	  f /  | 
	  f /  | 
	  f /  | 
	 
	
	  | 
	    A && B || C
	   | 
	  T /  | 
	  T /  X | 
	  T /  X | 
	  T /  X | 
	  f /  | 
	  f /  | 
	  T /  X | 
	  f /  | 
	 
	
	  | 
	    A || B && C
	   | 
	  T /  | 
	  T /  X | 
	  T /  X | 
	  T /  X | 
	  T /  X | 
	  f /  | 
	  f /  | 
	  f /  | 
	 
	
	  | 
	    A && B 
	   | 
	  T /  | 
	  T /  X | 
	  f /  | 
	  f /  | 
	  f /  | 
	  f /  | 
	  f /  | 
	  f /  | 
	 
	
	  | 
	    A && C
	   | 
	  T /  | 
	  f /  | 
	  T /  X | 
	  f /  | 
	  f /  | 
	  f /  | 
	  f /  | 
	  f /  | 
	 
	
	  | 
	     B && C
	   | 
	  T /  | 
	  f /  | 
	  f /  | 
	  T /  X | 
	  f /  | 
	  f /  | 
	  f /  | 
	  f /  | 
	 
       
     | 
   
 
  Remember all this assumes that tests powerful enough to catch
  one-token errors will catch more complicated (but still plausible)
  errors. A way to convince yourself is to try and find a variant
  of (A and B and C) that won't be caught by these test
  cases. Ask yourself if it's at all plausible that you'd make such an
  error. (Remember: we've already conceded faults of omission.) 
 
  These rules are easy to memorize. The cases for expressions that
  mix and and or are not. A long time ago, I
  wrote a program that generates probably-minimal test sets for any
  given boolean expression (including relational operators
  like a<b). Timothy Coulter and Curtis Pettit,
  students of Cem Kaner, made it
  more capable and gave it a web UI. Here it
  is: http://www.oneofthewolves.com/multi/applet.html. 
 
  When using the style I described earlier, I don't think you need
  multi, because I'm tentatively advocating always breaking tables that
  combine ands and ors into separate tables
  that do not.
 
 
  1 I can't remember if the transformations I used when
  working all this out included substituting one variable for another
  (like (A and B and A)). Multi, described after this
  footnote, doesn't. I don't think it would make a
  difference—certainly it doesn't in this particular example—but I'm not
  going to bother to check.
 
  2 I'm leaving what it means to "run
  a program" vague. That gets to the difference of whether the
  mutation is "weak" or
  "strong". See this post
  by Ivan Moore. I didn't find
  much in the online literature about mutation testing; if you want to
  know more, you'll have to go to the library. There are some starting
  references at the end
  of this
  paper (PDF).
 
## Posted at 08:36 in category /fit
[permalink] 
[top]
  
 
					 
      |