| Exploring Solution Spaces © Copyright 2003-2006, by C. Keith Ray | ||||||||||||||||||||||||
|
Archives
Subscribe |
2005.Sep.28 Wed I once needed to make three "filters" works. They each took an image file and several parameters as input, and produced an output PDF file. For most manual tests for various parameter settings, they did not produce correct output. So I refactored the big filter function to do the filtering in two steps (two subroutine calls). The first subroutine works on the parameters to compute the values needed to be passed into the image processing library, and the second subroutine calls the image processing library with the input image, those computed values, and produces the output image. Then I wrote a parameterized unit test that took all the parameters as input to the first subroutine, also took into expected output values, and asserted that the actual output values were equal to the expected output values. Then I wrote 900 one-line unit tests to this parameterized unit test with the various permutations of the input parameters and expected output values. (I actually worked in a spreadsheet for a while and copied-pasted from the spreadsheet to the unit test source code.) The test would also generate the output PDF file, which I could visually examine About 80% of the tests failed. Then I started working on the first subroutine to fix its problems. I got to where only 60% of the tests failed, then only 50%, 30%, 25%, 10%, the only 2 failures, and then 100% passing. Sometimes a change causes more tests to fail than before, and I could examine my latest modification and fix it, or undo it to try something else. That was the first of the three filters. The other two were similar, but with fewer parameters and options, each of those only needed about 600 one-line unit tests. Note that I didn't test EVERY combinations of options and settings. That would have required several billion test cases. By making an intelligent selection of the "input space" I could exercise the filter algorithm along all of its dimensions with a minimum of effort. Fixing all three filters with the aid of automated tests only took a few days each. Trying to make the algorithm work with manual testing would have taken months or years. Ruby, calling a method with a block argument:
anObject.methodTakingBlock { someObj.doSomethingHere }
or
anObject.methodTakingBlock do
someObj.doSomethingHere
end
Smalltalk, calling a method with a block argument:
anObject methodTakingBlock: [ someObj doSomethingHere ].
Ruby, implementing a method that takes a block argument (the lack of a named block variable and the "yield" keyword are a bit confusing, particularly if you associate "yield" with threads, which are not in play here.)
def methodTakingBlock
yield
end
Smalltalk, implementing a method that takes a block argument.
methodTakingBlock: aBlock
aBlock value.
Ruby, calling a method with a block argument that takes two argument itself:
anObject.methodTakingBlock {
|aVar, bVar| someObj.doSomethingHere(aVar, bVar) }
or
anObject.methodTakingBlock do |aVar, bVar|
someObj.doSomethingHere(aVar, bVar)
end
Smalltalk, calling a method with a block argument that takes two arguments itself:
anObject methodTakingBlock: [ :aVar :bVar |
someObj doSomethingHere: aVar andHere: bVar ].
Ruby, implementing a method that takes a block argument, giving the block the values 12 and 13 for the block's own arguments.
def methodTakingBlock
yield 12, 13
end
An Ruby alternative syntax where the block argument is converted to a Proc object and is accessible via a named parameter:
def methodTakingBlock(&aProc)
aProc.call(12,13)
end
Smalltalk, implementing a method that takes a block argument, giving the block values 12 and 13 for its own arguments.
methodTakingBlock: aBlock
aBlock value: 12 value: 13.
|
|||||||||||||||||||||||