[Pharo-project] Stubbing class-side methods
I keep coming up against this situation over and over in testing. MyClass>>doSomethingWorthTesting ... now := DateAndTime now. ... I want to control DateAndTime class>>now to make this test determinate and simulate interesting edge cases, but can't figure out a good way. In Ruby, I could easily stub it because it's not a live system (or use dependency injection), but here I don't want to mess with the real DateAndTime class in case something goes wrong and leaves my system dirty. I really hate using dependency injection for this kind of thing (especially without Ruby's default parameter values). I've been creating MyClass>>now and stubbing that. Am I being paranoid, should I just hijack the real class and fix it when I'm done? What's the best way to do this? Thanks. Sean -- View this message in context: http://forum.world.st/Stubbing-class-side-methods-tp3674065p3674065.html Sent from the Pharo Smalltalk mailing list archive at Nabble.com.
is MyClass a domain class or a (unit) test class? thanks, Mike On Sun, Jul 17, 2011 at 10:45 PM, Sean P. DeNigris <sean@clipperadams.com> wrote:
I keep coming up against this situation over and over in testing.
MyClass>>doSomethingWorthTesting   ...   now := DateAndTime now.   ...
I want to control DateAndTime class>>now to make this test determinate and simulate interesting edge cases, but can't figure out a good way. In Ruby, I could easily stub it because it's not a live system (or use dependency injection), but here I don't want to mess with the real DateAndTime class in case something goes wrong and leaves my system dirty.
I really hate using dependency injection for this kind of thing (especially without Ruby's default parameter values). I've been creating MyClass>>now and stubbing that. Am I being paranoid, should I just hijack the real class and fix it when I'm done? What's the best way to do this?
Thanks. Sean
-- View this message in context: http://forum.world.st/Stubbing-class-side-methods-tp3674065p3674065.html Sent from the Pharo Smalltalk mailing list archive at Nabble.com.
Michael Roberts-2 wrote:
is MyClass a domain class
Yes -- View this message in context: http://forum.world.st/Stubbing-class-side-methods-tp3674065p3675827.html Sent from the Pharo Smalltalk mailing list archive at Nabble.com.
Personally when I come across cases like this I either break the (usually public) method into a public and internal method, the internal method contains the date so we can test the edge cases. E.g. MyClass>> doSomethingWorthTesting ... basicDoSomethingWorthTestingAt: DateAndTime now. ... My test then uses the internal method for testing where I can pass in different dates. Another option which you've probably decided against is to delegate to your current class or a Registry to ask for the class with which to use for date related tasks, a kind of factory in a way. MyClass>>>> doSomethingWorthTesting ... now := self class dateClass now. ... I personally feel Pharo is moving into a more pluggable direction with the work on the environment (Smalltalk environment) where we can provide our own environment for a particular context, something similar to Newspeaks nested classes and imports using the usingPlatform: construct. E.g. class MyClassModule usingPlatform: platform = ( private DateAndTime = platform time DateAndTime. private Error = platform exceptions Error. ) Hmm maybe we can do a lightweight version using DynamicBindling library myEnvironmentBinding bindDuring: [ ...your testcase... ] Cheers Carlo On 17 Jul 2011, at 11:45 PM, Sean P. DeNigris wrote: I keep coming up against this situation over and over in testing. MyClass>>doSomethingWorthTesting ... now := DateAndTime now. ... I want to control DateAndTime class>>now to make this test determinate and simulate interesting edge cases, but can't figure out a good way. In Ruby, I could easily stub it because it's not a live system (or use dependency injection), but here I don't want to mess with the real DateAndTime class in case something goes wrong and leaves my system dirty. I really hate using dependency injection for this kind of thing (especially without Ruby's default parameter values). I've been creating MyClass>>now and stubbing that. Am I being paranoid, should I just hijack the real class and fix it when I'm done? What's the best way to do this? Thanks. Sean -- View this message in context: http://forum.world.st/Stubbing-class-side-methods-tp3674065p3674065.html Sent from the Pharo Smalltalk mailing list archive at Nabble.com.
Let's get rid of classes? :) 2011/7/18 Carlo <snoobabk@yahoo.ie>
Personally when I come across cases like this I either break the (usually public) method into a public and internal method, the internal method contains the date so we can test the edge cases.
E.g. MyClass>> doSomethingWorthTesting ... basicDoSomethingWorthTestingAt: DateAndTime now. ...
My test then uses the internal method for testing where I can pass in different dates.
Another option which you've probably decided against is to delegate to your current class or a Registry to ask for the class with which to use for date related tasks, a kind of factory in a way. MyClass>>>> doSomethingWorthTesting ... now := self class dateClass now. ...
I personally feel Pharo is moving into a more pluggable direction with the work on the environment (Smalltalk environment) where we can provide our own environment for a particular context, something similar to Newspeaks nested classes and imports using the usingPlatform: construct. E.g. class MyClassModule usingPlatform: platform = ( private DateAndTime = platform time DateAndTime. private Error = platform exceptions Error. )
Hmm maybe we can do a lightweight version using DynamicBindling library myEnvironmentBinding bindDuring: [ ...your testcase... ]
Cheers Carlo
On 17 Jul 2011, at 11:45 PM, Sean P. DeNigris wrote:
I keep coming up against this situation over and over in testing.
MyClass>>doSomethingWorthTesting ... now := DateAndTime now. ...
I want to control DateAndTime class>>now to make this test determinate and simulate interesting edge cases, but can't figure out a good way. In Ruby, I could easily stub it because it's not a live system (or use dependency injection), but here I don't want to mess with the real DateAndTime class in case something goes wrong and leaves my system dirty.
I really hate using dependency injection for this kind of thing (especially without Ruby's default parameter values). I've been creating MyClass>>now and stubbing that. Am I being paranoid, should I just hijack the real class and fix it when I'm done? What's the best way to do this?
Thanks. Sean
-- View this message in context: http://forum.world.st/Stubbing-class-side-methods-tp3674065p3674065.html Sent from the Pharo Smalltalk mailing list archive at Nabble.com.
-- Dennis Schetinin
Carlo wrote:
Personally when I come across cases like this I either break the (usually public) method into a public and internal method, the internal method contains the date so we can test the edge cases.
The thing I don't like about this is that I often load a package I've never used before and go to the tests, which I believe should be examples of how to use the class, yet I find tests that are not from the POV of a user and give me less direction than I'd like. With this method, the sin-to-make-it-testable is on display in the action step of the test instead of hidden away in the setup as it could be with a stub. Carlo wrote:
Another option which you've probably decided against is to delegate to your current class or a Registry to ask for the class with which to use for date related tasks, a kind of factory in a way.
Yes, this is the best way I can think of, but it feels like a hack. Carlo wrote:
I personally feel Pharo is moving into a more pluggable direction with the work on the environment (Smalltalk environment) where we can provide our own environment for a particular context
That would be awesome! Thanks. Sean -- View this message in context: http://forum.world.st/Stubbing-class-side-methods-tp3674065p3675843.html Sent from the Pharo Smalltalk mailing list archive at Nabble.com.
On 18 July 2011 18:52, Sean P. DeNigris <sean@clipperadams.com> wrote:
Carlo wrote:
Personally when I come across cases like this I either break the (usually public) method into a public and internal method, the internal method contains the date so we can test the edge cases.
The thing I don't like about this is that I often load a package I've never used before and go to the tests, which I believe should be examples of how to use the class, yet I find tests that are not from the POV of a user and give me less direction than I'd like. With this method, the sin-to-make-it-testable is on display in the action step of the test instead of hidden away in the setup as it could be with a stub.
Carlo wrote:
Another option which you've probably decided against is to delegate to your current class or a Registry to ask for the class with which to use for date related tasks, a kind of factory in a way.
Yes, this is the best way I can think of, but it feels like a hack.
Carlo wrote:
I personally feel Pharo is moving into a more pluggable direction with the work on the environment (Smalltalk environment) where we can provide our own environment for a particular context
That would be awesome!
This looks like a relevant: http://code.google.com/p/pharo/issues/detail?id=4485
Thanks. Sean
-- View this message in context: http://forum.world.st/Stubbing-class-side-methods-tp3674065p3675843.html Sent from the Pharo Smalltalk mailing list archive at Nabble.com.
-- Best regards, Igor Stasenko AKA sig.
Where is the code where class names actually get looked up? I put halts in Class>>bindingOf:, Class>>environment, and the SmalltalkImage>>at: methods, but nothing hit. Also, how would I have found this info for myself without asking on the list, since stepping into a debugger doesn't help? Thanks. Sean -- View this message in context: http://forum.world.st/Stubbing-class-side-methods-tp3674065p3676163.html Sent from the Pharo Smalltalk mailing list archive at Nabble.com.
On Mon, Jul 18, 2011 at 12:11 PM, Sean P. DeNigris <sean@clipperadams.com>wrote:
Where is the code where class names actually get looked up?
In the compiler. What gets added to the method is the association for the variable. The push/store/pop literal variable bytecodes then indirect through the association to get and set its value inst var.
I put halts in Class>>bindingOf:, Class>>environment, and the SmalltalkImage>>at: methods, but nothing hit. Also, how would I have found this info for myself without asking on the list, since stepping into a debugger doesn't help?
Thanks. Sean
-- View this message in context: http://forum.world.st/Stubbing-class-side-methods-tp3674065p3676163.html Sent from the Pharo Smalltalk mailing list archive at Nabble.com.
-- best, Eliot
Ah, so the lookups are done once when the method is compiled and then stored in the CompiledMethod... My vision is to write something like: cannedNow := DateAndTime stub: #now; andReturn: [ DateAndTime year: 2011 month: 12 day: 24 hour: 12 minute: 34 second: 55 ]. testableClass := MyClass usingDouble: cannedNow. The first line seems easy, and for the second line, I have so far (very rough, but working): | class copy | class := MyClass. copy := MyClass copy. copy methods do: [ :m | | classRef | (m hasLiteralSuchThat: [ :l | ((l isKindOf: Association) and: [ l key = #DateAndTime])]) ifTrue: [ | newMeth refIndex | newMeth := copy methodDictionary at: m selector put: m copy. refIndex := newMeth indexOfLiteralSuchThat: [ :l | ((l isKindOf: Association) and: [ l key = #DateAndTime])]. newMeth literalAt: refIndex put: #DateAndTime->MockedClass ] ]. ^ copy. Where CompiledMethod>>indexOfLiteralSuchThat: aBlock "Answer the literal index of the argument, literal, or zero if none." 2 to: self numLiterals - 1 "exclude superclass + selector/properties" do: [:index | (aBlock value: (self objectAt: index)) ifTrue: [^index - 1]]. ^0 Thanks again. Sean -- View this message in context: http://forum.world.st/Stubbing-class-side-methods-tp3674065p3676637.html Sent from the Pharo Smalltalk mailing list archive at Nabble.com.
On 19 July 2011 01:02, Sean P. DeNigris <sean@clipperadams.com> wrote:
Ah, so the lookups are done once when the method is compiled and then stored in the CompiledMethod...
My vision is to write something like: Â Â Â Â cannedNow := DateAndTime stub: #now; andReturn: [ DateAndTime year: 2011 month: 12 day: 24 hour: 12 minute: 34 second: 55 ]. Â Â Â Â testableClass := MyClass usingDouble: cannedNow.
The first line seems easy, and for the second line, I have so far (very rough, but working):
    | class copy |     class := MyClass.     copy := MyClass copy.
    copy methods do: [ :m | | classRef |         (m hasLiteralSuchThat: [ :l | ((l isKindOf: Association) and: [ l key = #DateAndTime])])             ifTrue: [ | newMeth refIndex |                 newMeth := copy methodDictionary at: m selector put: m copy.                 refIndex := newMeth indexOfLiteralSuchThat: [ :l | ((l isKindOf: Association) and: [ l key = #DateAndTime])].                 newMeth literalAt: refIndex put: #DateAndTime->MockedClass ] ].     ^ copy.
Where CompiledMethod>>indexOfLiteralSuchThat: aBlock     "Answer the literal index of the argument, literal, or zero if none."     2 to: self numLiterals - 1 "exclude superclass + selector/properties"      do:         [:index |         (aBlock value: (self objectAt: index)) ifTrue: [^index - 1]].     ^0
you doing something really complex. on your place i would just implement #dateAndTimeClass so, you can just write self dateAndTimeClass new and depending on implementor it would answer one or another class.
Thanks again. Sean
-- View this message in context: http://forum.world.st/Stubbing-class-side-methods-tp3674065p3676637.html Sent from the Pharo Smalltalk mailing list archive at Nabble.com.
-- Best regards, Igor Stasenko AKA sig.
Igor Stasenko wrote:
you doing something really complex. on your place i would just implement #dateAndTimeClass
I'm playing :) And, working on ideas for our testing frameworks. Partially stubbing class-side methods is something I miss from rspec in Ruby (although in this case adding one #dateAndTimeClass method is not a huge deal) Sean -- View this message in context: http://forum.world.st/Stubbing-class-side-methods-tp3674065p3676800.html Sent from the Pharo Smalltalk mailing list archive at Nabble.com.
I should add it to Mocketry. 2011/7/19 Sean P. DeNigris <sean@clipperadams.com>
Ah, so the lookups are done once when the method is compiled and then stored in the CompiledMethod...
My vision is to write something like: cannedNow := DateAndTime stub: #now; andReturn: [ DateAndTime year: 2011 month: 12 day: 24 hour: 12 minute: 34 second: 55 ]. testableClass := MyClass usingDouble: cannedNow.
The first line seems easy, and for the second line, I have so far (very rough, but working):
| class copy | class := MyClass. copy := MyClass copy.
copy methods do: [ :m | | classRef | (m hasLiteralSuchThat: [ :l | ((l isKindOf: Association) and: [ l key = #DateAndTime])]) ifTrue: [ | newMeth refIndex | newMeth := copy methodDictionary at: m selector put: m copy. refIndex := newMeth indexOfLiteralSuchThat: [ :l | ((l isKindOf: Association) and: [ l key = #DateAndTime])]. newMeth literalAt: refIndex put: #DateAndTime->MockedClass ] ]. ^ copy.
Where CompiledMethod>>indexOfLiteralSuchThat: aBlock "Answer the literal index of the argument, literal, or zero if none." 2 to: self numLiterals - 1 "exclude superclass + selector/properties" do: [:index | (aBlock value: (self objectAt: index)) ifTrue: [^index - 1]]. ^0
Thanks again. Sean
-- View this message in context: http://forum.world.st/Stubbing-class-side-methods-tp3674065p3676637.html Sent from the Pharo Smalltalk mailing list archive at Nabble.com.
Can we test self sends this way? 2011/7/19 Denis Kudriashov <dionisiydk@gmail.com>
I should add it to Mocketry.
2011/7/19 Sean P. DeNigris <sean@clipperadams.com>
Ah, so the lookups are done once when the method is compiled and then
stored in the CompiledMethod...
My vision is to write something like: cannedNow := DateAndTime stub: #now; andReturn: [ DateAndTime year: 2011 month: 12 day: 24 hour: 12 minute: 34 second: 55 ]. testableClass := MyClass usingDouble: cannedNow.
The first line seems easy, and for the second line, I have so far (very rough, but working):
| class copy | class := MyClass. copy := MyClass copy.
copy methods do: [ :m | | classRef | (m hasLiteralSuchThat: [ :l | ((l isKindOf: Association) and: [ l key = #DateAndTime])]) ifTrue: [ | newMeth refIndex | newMeth := copy methodDictionary at: m selector put: m copy. refIndex := newMeth indexOfLiteralSuchThat: [ :l | ((l isKindOf: Association) and: [ l key = #DateAndTime])]. newMeth literalAt: refIndex put: #DateAndTime->MockedClass ] ]. ^ copy.
Where CompiledMethod>>indexOfLiteralSuchThat: aBlock "Answer the literal index of the argument, literal, or zero if none." 2 to: self numLiterals - 1 "exclude superclass + selector/properties" do: [:index | (aBlock value: (self objectAt: index)) ifTrue: [^index - 1]]. ^0
Thanks again. Sean
-- View this message in context: http://forum.world.st/Stubbing-class-side-methods-tp3674065p3676637.html Sent from the Pharo Smalltalk mailing list archive at Nabble.com.
-- Dennis Schetinin
I think not. This way is substitution of method literal. But "self sends" stubbing required anonimous subclass creation with required methods overrides. 2011/7/19 Dennis Schetinin <chaetal@gmail.com>
Can we test self sends this way?
2011/7/19 Denis Kudriashov <dionisiydk@gmail.com>
I should add it to Mocketry.
2011/7/19 Sean P. DeNigris <sean@clipperadams.com>
Ah, so the lookups are done once when the method is compiled and then
stored in the CompiledMethod...
My vision is to write something like: cannedNow := DateAndTime stub: #now; andReturn: [ DateAndTime year: 2011 month: 12 day: 24 hour: 12 minute: 34 second: 55 ]. testableClass := MyClass usingDouble: cannedNow.
The first line seems easy, and for the second line, I have so far (very rough, but working):
| class copy | class := MyClass. copy := MyClass copy.
copy methods do: [ :m | | classRef | (m hasLiteralSuchThat: [ :l | ((l isKindOf: Association) and: [ l key = #DateAndTime])]) ifTrue: [ | newMeth refIndex | newMeth := copy methodDictionary at: m selector put: m copy. refIndex := newMeth indexOfLiteralSuchThat: [ :l | ((l isKindOf: Association) and: [ l key = #DateAndTime])]. newMeth literalAt: refIndex put: #DateAndTime->MockedClass ] ]. ^ copy.
Where CompiledMethod>>indexOfLiteralSuchThat: aBlock "Answer the literal index of the argument, literal, or zero if none." 2 to: self numLiterals - 1 "exclude superclass + selector/properties" do: [:index | (aBlock value: (self objectAt: index)) ifTrue: [^index - 1]]. ^0
Thanks again. Sean
-- View this message in context: http://forum.world.st/Stubbing-class-side-methods-tp3674065p3676637.html Sent from the Pharo Smalltalk mailing list archive at Nabble.com.
-- Dennis Schetinin
Denis Kudriashov wrote:
I think not. This way is substitution of method literal.
But "self sends" stubbing required anonimous subclass creation with required methods overrides.
That sounds right. The bytecodes would be different if a self send was rerouted to another object (pushLit: instead of self). Sean -- View this message in context: http://forum.world.st/Stubbing-class-side-methods-tp3674065p3678562.html Sent from the Pharo Smalltalk mailing list archive at Nabble.com.
Denis Kudriashov wrote:
I should add it to Mocketry.
Feel free. It's MIT. I'm still marinading over a TestCase method that loops through it's own literals and makes the swap for each. Then instead of #usingDouble:, it would be something like #testXxx self usingDouble: ... which would call #usingDouble: on each class referenced in the test, creating a simulated environment for the test. -- View this message in context: http://forum.world.st/Stubbing-class-side-methods-tp3674065p3678530.html Sent from the Pharo Smalltalk mailing list archive at Nabble.com.
вÑоÑник, 19 иÑÐ»Ñ 2011 г. полÑзоваÑÐµÐ»Ñ Sean P. DeNigris <sean@clipperadams.com> пиÑал:
#testXxx   self usingDouble: ...
which would call #usingDouble: on each class referenced in the test, creating a simulated environment for the test.
Good idea
participants (7)
-
Carlo -
Denis Kudriashov -
Dennis Schetinin -
Eliot Miranda -
Igor Stasenko -
Michael Roberts -
Sean P. DeNigris