How do I replace a message body programmatically in Smalltalk?
As part of some testing, I'm looking to, temporarily, replace the contents of a message with one that will return a predictable set of test values. What is the standard Smalltalk-y way to do this type of work? Is there some sample code for me to look at?
Some clarification:
- No, this is not a simple unit test. I'm testing a 开发者_如何学Golarge, complex, system.
- Replacing the whole object at runtime is impractical. Significant accumulated state would have to be twiddled by the test, beyond replacing the method.
- Subclassing and replacing one method doesn't generalize. I'm testing dozens of similar classes and thousands of objects. Filling up the class hierarchy with tiny one method classes, one for each test-case, would really suck - and it would suck even more to have to update them if I change my test case.
Here's some pseudocode for what I'm looking to write:
replaceMessage: 'fibonacci'
onClass: 'funFunctions'
with: 'returnsPredictableNumbersWithoutCalculatingThem'.
self runTestSet1.
restoreMessage: 'fibonacci'
onClass: 'funFunctions'.
self runFollowUpSet1. "Depends on state set by Set1"
replaceMessage: 'fibonacci'
onClass: 'funFunctions'
with: 'returnsPredictableNumbersWithoutCalculatingThemPart2'.
self runFollowUpSet2. "Depends on state set by followupset1"
restoreMessage: 'fibonacci'
onClass: 'funFunctions'.
To replace one message with another you could:
a) in corresponding method(s), change the selector, which responsible for given message send.
b) use proxies, to wrap all objects of interest and intercept given message in order to use different evaluation path than in original method.
A way to do this is using MethodWrappers or similar. There is one thing called "Objects as Methods", which is supported in some Smalltalk dialects like Pharo and Squeak (not sure about others). The idea is that you can use any object as method. So, you can do this:
MyClass methodDict at: #foo put: MyClassAsMethod new.
And in MyClassMethod you have to implement #run: aSelector with: arguments in: aReceiver
So, when you do: MyClass new foo. Then the VM will see that what you have there is NOT a CompiledMethod but instead another object, and then it will send to such object the message #run:with:in:
If I were you, I would download pharo from here: https://gforge.inria.fr/frs/download.php/27524/Pharo-1.1.1-OneClickCogVM.zip
and would investigate the package MethodWrappers and all its classes.
Maybe MethodWrappers - ECOOP paper Wrappers to the Rescue
My first thought would be to sub-class and override only the particular message you want to return test values. However, you'll probably want to read the subclass to test and the subclass to test anti-pattern entries on Ward's wiki.
To make this temporary, you would need to replace the test object with an instance of the sub-class, then change it back to the original class under test when you're done.
精彩评论