New class factory for tests (and other use cases)

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
9 messages Options
Reply | Threaded
Open this post in threaded view
|

New class factory for tests (and other use cases)

Adrian Kuhn
`ClassFactoryForTestCase` is a awesome tool when you need to create throw-away
 classes for tests. And the new akuhn/SUnit includes a DSL that makes creating
 anonymous classes even simpler!

The new DSL is put in a new class to avoid compatibility issues. To create an
 anonymous class just do

    class := ClassFactory newClass.

this creates a new subclass of Object. The creates classes is automatically
 reclaimed by garbage collection when no longer used. That is, tear down of
 class factory is not required anymore.

The class is created without logging and not registered with the system. That
 is, `Object subclasses` does not include the created class.

To create a subclass of a specific class do

    class := ClassFactory newSubclass: Point.

To create a subclass with accessors do

    class := ClassFactory newSubclass: Point with: [ :f |
        f compileAccessorsFor: 'color' ].

If you want getters or setters only, use `#compileSetterFor:` or
 `#compilerGetterFor:`. If you want an instance variable without accessor, use
 `#declareInstVar:`.  

To create a subclass with methods do

    class := ClassFactory newSubclass: Point with: [ :f |
        f compile: 'answer ^ 42' ].

NB: please note that class factory compiles silently without logging.

To create a method on the class side do

    class := ClassFactory newSubclass: Point with: [ :f |
        f forClass: [ :cf |
            cf compile: 'somePoint ^ self x: 2 y: 3' ]].

In fact, the class side factory `cf` supports the same protocol as the instance
 side factory. Please note that creating instance variables on the class side
 creates "class instances variables" and not "instance class variables". That
 is, they are local to the creates class but not to subclasses of the created
 class. Typically this should not be a limitation since you only create one
 class without further subclasses.

Also, please note that the created classes and all instances created from that
 class are ment to be thrown away after their use. The created class is not
 registered with the system, and thus when you change the instance size of its
 superclass the created instances will not be updated.

Gofer it
    disablePackageCache;
    squeaksource: 'akuhn';
    package: 'SUnit';
    package: 'SUnitGUI';
    load

--AA



_______________________________________________
Pharo-project mailing list
[hidden email]
http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project
Reply | Threaded
Open this post in threaded view
|

Re: New class factory for tests (and other use cases)

Stéphane Ducasse
> To create a method on the class side do
>
>    class := ClassFactory newSubclass: Point with: [ :f |
>        f forClass: [ :cf |
>            cf compile: 'somePoint ^ self x: 2 y: 3' ]].

the forClass: is not really good to indicate class side.
Then why all these blocks?
Do we need a specific DSL (it looks to me that smalltalk got into dsl plague).
why without the block is it not good:

 class := ClassFactory newSubclass: Point with: [ :f |
       f metaSide compile: 'somePoint ^ self x: 2 y: 3'.
       f compile: '+ arg ^ self x + arg x ..... 3' ].

 ].




_______________________________________________
Pharo-project mailing list
[hidden email]
http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project
Reply | Threaded
Open this post in threaded view
|

Re: New class factory for tests (and other use cases)

Adrian Kuhn
Stéphane Ducasse <stephane.ducasse@...> writes:

>  class := ClassFactory newSubclass: Point with: [ :f |
>        f metaSide compile: 'somePoint ^ self x: 2 y: 3'.
>        f compile: '+ arg ^ self x + arg x ..... 3' ].
>  ].

Looks nicer, gekauft :)

--AA




_______________________________________________
Pharo-project mailing list
[hidden email]
http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project
Reply | Threaded
Open this post in threaded view
|

Re: New class factory for tests (and other use cases)

Stéphane Ducasse
:)

cool
bezalht :)

Stef

On Dec 29, 2009, at 11:05 AM, Adrian Kuhn wrote:

> Stéphane Ducasse <stephane.ducasse@...> writes:
>
>> class := ClassFactory newSubclass: Point with: [ :f |
>>       f metaSide compile: 'somePoint ^ self x: 2 y: 3'.
>>       f compile: '+ arg ^ self x + arg x ..... 3' ].
>> ].
>
> Looks nicer, gekauft :)
>
> --AA
>
>
>
>
> _______________________________________________
> Pharo-project mailing list
> [hidden email]
> http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project


_______________________________________________
Pharo-project mailing list
[hidden email]
http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project
Reply | Threaded
Open this post in threaded view
|

Re: New class factory for tests (and other use cases)

Alexandre Bergel
In reply to this post by Adrian Kuhn
Hi Adrian,

I like the idea of your class factory. For years I have been using  
something very similar to create throwable classes. For example, in  
the package Spy (available on squeaksource) I have a class  
AbstractSpyTest and the following methods:
AbstractSpyTest>>createClassNamed:
AbstractSpyTest>>createClassNamed:superclass:

then, within a test method, I can do:
testMyExample
   | cls |
   cls := self createClassNamed: #C1.
   cls compile: 'giveMyFive  ^ 5'.
   ...

in the tearDown method I have:
AbstractSpyTest>>tearDown
        super tearDown.
        classes ifNotNil: [:clss | clss do: #removeFromSystem ].

where 'classes' is an instance variable of AbstractSpyTest

I used this piece of code for many years already, and I have always  
been happy so far. I also take care that the classes are created in a  
class category different from the one of my application.

You propose:
> class := ClassFactory newSubclass: Point with: [ :f |
>       f metaSide compile: 'somePoint ^ self x: 2 y: 3'.
>       f compile: '+ arg ^ self x + arg x ..... 3' ].
>
> ].


I find this difficult to remember. Do you manage automatic class  
removing in the tearDown?

Cheers,
Alexandre


On 28 Dec 2009, at 23:07, Adrian Kuhn wrote:

> `ClassFactoryForTestCase` is a awesome tool when you need to create  
> throw-away
> classes for tests. And the new akuhn/SUnit includes a DSL that makes  
> creating
> anonymous classes even simpler!
>
> The new DSL is put in a new class to avoid compatibility issues. To  
> create an
> anonymous class just do
>
>    class := ClassFactory newClass.
>
> this creates a new subclass of Object. The creates classes is  
> automatically
> reclaimed by garbage collection when no longer used. That is, tear  
> down of
> class factory is not required anymore.
>
> The class is created without logging and not registered with the  
> system. That
> is, `Object subclasses` does not include the created class.
>
> To create a subclass of a specific class do
>
>    class := ClassFactory newSubclass: Point.
>
> To create a subclass with accessors do
>
>    class := ClassFactory newSubclass: Point with: [ :f |
>        f compileAccessorsFor: 'color' ].
>
> If you want getters or setters only, use `#compileSetterFor:` or
> `#compilerGetterFor:`. If you want an instance variable without  
> accessor, use
> `#declareInstVar:`.
>
> To create a subclass with methods do
>
>    class := ClassFactory newSubclass: Point with: [ :f |
>        f compile: 'answer ^ 42' ].
>
> NB: please note that class factory compiles silently without logging.
>
> To create a method on the class side do
>
>    class := ClassFactory newSubclass: Point with: [ :f |
>        f forClass: [ :cf |
>            cf compile: 'somePoint ^ self x: 2 y: 3' ]].
>
> In fact, the class side factory `cf` supports the same protocol as  
> the instance
> side factory. Please note that creating instance variables on the  
> class side
> creates "class instances variables" and not "instance class  
> variables". That
> is, they are local to the creates class but not to subclasses of the  
> created
> class. Typically this should not be a limitation since you only  
> create one
> class without further subclasses.
>
> Also, please note that the created classes and all instances created  
> from that
> class are ment to be thrown away after their use. The created class  
> is not
> registered with the system, and thus when you change the instance  
> size of its
> superclass the created instances will not be updated.
>
> Gofer it
>    disablePackageCache;
>    squeaksource: 'akuhn';
>    package: 'SUnit';
>    package: 'SUnitGUI';
>    load
>
> --AA
>
>
>
> _______________________________________________
> Pharo-project mailing list
> [hidden email]
> http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project
>

--
_,.;:~^~:;._,.;:~^~:;._,.;:~^~:;._,.;:~^~:;._,.;:
Alexandre Bergel  http://www.bergel.eu
^~:;._,.;:~^~:;._,.;:~^~:;._,.;:~^~:;._,.;:~^~:;.






_______________________________________________
Pharo-project mailing list
[hidden email]
http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project
Reply | Threaded
Open this post in threaded view
|

Re: New class factory for tests (and other use cases)

Adrian Kuhn
Alexandre Bergel <alexandre@...> writes:

> Do you manage automatic class removing in the tearDown?

Created classes are not registered with the system, so garbage collection will
 reclaim them when no longer used---unless there is a hidden class cache of
 which I am not aware. I have found a class names cache but not a class cache.

> You propose:
> > class := ClassFactory newSubclass: Point with: [ :f |
> >       f metaSide compile: 'somePoint ^ self x: 2 y: 3'.
> >       f compile: '+ arg ^ self x + arg x ..... 3' ].
> > ].
>
> I find this difficult to remember.

What is difficult to remember, the API names?

By the way, the rational for using a block is that the class is only built in
 the end. The class cannot be built in the beginnen, since I want to allow the
 addition of instance variables any time while building it. The motivation is
 that clients should be able to loop over some array and add accessors one by
 one. For example as in

    class := ClassFactory newSubclassWith: [ :f |
        array do: [ :each | f compileAccessorFor: each. ]]
       
which behind the scenes keeps adding new variables to class under construction.
 A future feature might even be initialization support ...

--AA


_______________________________________________
Pharo-project mailing list
[hidden email]
http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project
Reply | Threaded
Open this post in threaded view
|

Re: New class factory for tests (and other use cases)

Alexandre Bergel
>> You propose:
>>> class := ClassFactory newSubclass: Point with: [ :f |
>>>      f metaSide compile: 'somePoint ^ self x: 2 y: 3'.
>>>      f compile: '+ arg ^ self x + arg x ..... 3' ].
>>> ].
>>
>> I find this difficult to remember.
>
> What is difficult to remember, the API names?

I feel yes. In the code you gave, there are 4 hard-to-remember  
keywords (ClassFactory, newSubclass:, with:, metaSide), without  
counting the block declaration.
Variable declaration is possible using a method  
#createClassNamed:superclass:ivs:

Cheers,
Alexandre
--
_,.;:~^~:;._,.;:~^~:;._,.;:~^~:;._,.;:~^~:;._,.;:
Alexandre Bergel  http://www.bergel.eu
^~:;._,.;:~^~:;._,.;:~^~:;._,.;:~^~:;._,.;:~^~:;.






_______________________________________________
Pharo-project mailing list
[hidden email]
http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project
Reply | Threaded
Open this post in threaded view
|

Re: New class factory for tests (and other use cases)

Adrian Kuhn
Alexandre Bergel <alexandre@...> writes:

> I feel yes. In the code you gave, there are 4 hard-to-remember  
> keywords (ClassFactory, newSubclass:, with:, metaSide), without  
> counting the block declaration.
> Variable declaration is possible using a method  
> #createClassNamed:superclass:ivs:

It's a new API, so you have to learn it, yes.

It's regular and rather small though. You start with

    #newClass
    #newClassWith: aBlock
    #newSubclass: aClass
    #newSubclass: aClass with: aBlock

and continue within the block with

    #classSide ...
    #declareInstVar: name
    #compileGetterFor: name
    #compileSetterFor: name
    #compileAccessorsFor: name
    #compile: sourceString

of which you typically need the last two only.

C'est tout :)

--AA



_______________________________________________
Pharo-project mailing list
[hidden email]
http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project
Reply | Threaded
Open this post in threaded view
|

Re: New class factory for tests (and other use cases)

Alexandre Bergel
Yeah, I will give a try

Alexandre


On 30 Dec 2009, at 17:54, Adrian Kuhn wrote:

> Alexandre Bergel <alexandre@...> writes:
>
>> I feel yes. In the code you gave, there are 4 hard-to-remember
>> keywords (ClassFactory, newSubclass:, with:, metaSide), without
>> counting the block declaration.
>> Variable declaration is possible using a method
>> #createClassNamed:superclass:ivs:
>
> It's a new API, so you have to learn it, yes.
>
> It's regular and rather small though. You start with
>
>    #newClass
>    #newClassWith: aBlock
>    #newSubclass: aClass
>    #newSubclass: aClass with: aBlock
>
> and continue within the block with
>
>    #classSide ...
>    #declareInstVar: name
>    #compileGetterFor: name
>    #compileSetterFor: name
>    #compileAccessorsFor: name
>    #compile: sourceString
>
> of which you typically need the last two only.
>
> C'est tout :)
>
> --AA
>
>
>
> _______________________________________________
> Pharo-project mailing list
> [hidden email]
> http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project
>

--
_,.;:~^~:;._,.;:~^~:;._,.;:~^~:;._,.;:~^~:;._,.;:
Alexandre Bergel  http://www.bergel.eu
^~:;._,.;:~^~:;._,.;:~^~:;._,.;:~^~:;._,.;:~^~:;.






_______________________________________________
Pharo-project mailing list
[hidden email]
http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project