Last updated at 12:41 am UTC on 17 January 2006
by Markus Gaelli
If you start to program "test-first," you really start to program "example-first".
When you have examples for your system, you easily can run it and increase the coverage of the code executed. This coverage helps you to deploy minimal systems and make them more performant, as just in time compilers can be trained on them.
But examples are also essential for documenting and understanding a system.
Eg will be a collaborative space, which supports programmers to create and retrieve examples for objects and their behavior.
Agile programming and E-Toys
Agile programming and E-Toys are similar.
It is common knowledge, that the hard part of testing is the creation of the test-scenario. If you look into Unit-Tests, the code to setup the test is usually much longer than the call of the method or the following assertions. And the setUp comes first.
The same holds for E-Toys programming: first comes the concrete object, only then (and not always!) comes the abstraction.
So what do you do, when you want to program a new behavior and you are the kind of programmer, who likes to "do it" in the "debugger", because it is such a lively place?
This is exactly what you do, when you program with E-Toys:
- You fetch all example-objects you need for this method, which is the receiver and the parameters.
- If they are not there yet, step back and compose them recursively with an Example-Object-Composer, until you have an example-object everywhere.
- Give the resulting object a name
- Define your method here, go in the debugger. (Or run it directly and use the newly created define-button in the debugger, after it fired up a DNU) and implement the desired behavior on the receiver-example-object.
- Only satisfy your needs for the concrete objects you play with, don't look into the future: "you ain't gonna need it!" Add assertions into the code, play around with this object and its behavior until you are sure that it does what you want.
- you take existing objects
- you compose them
- you name them,
- you add behavior to them
- and you play with them as long as necessary.
I find it strange, that in test-first-programming with current object-oriented systems, you are supposed to write simple tests, which only hold for certain objects, but you start to satisfy them by writing methods, which work for any object as a receiver and any objects as parameters.
This is a bad smell, as I think that is very valuable to keep the complexity of the test and the program at the same level, look at the result of Daniel Hillis, when he co-evolved sorting-algorithms on the connection-machine:
He came much earlier to good results, when the complexity of the program (=sorting-algorithm) became bigger with the complexity of the test. (=Degree of order of the list, which was to be sorted). By introducing example-methods, the complexity of the first versions of the program better fits to the complexity of the first version of the test.
To co-evolve the test and the program means then, to insert abstract assertions into the program while you come up with more abstract solutions around your abstract assertions.
The examples are kept to be able to still execute the program and hence the assertions.
Building-Blocks and Tools
The building blocks of an object-oriented system are objects and methods, so these are also the entities we will exemplify:
- Objects: example-objects
- Methods: example-methods
- Why can't I share the fixtures, which are built in unit-test-cases?
- Why can't I compose this fixtures easily and have to come up with mock-objects?
- Why not using Self but a kind of hybrid model? Because in Self are no tools or conceptual mindsets available,which help the programmer to think about abstraction. If he sees the example/instance/class browser of habitat in front of him, he is always reminded, that the example is an example for some higher order concept like a class.
Example-objects act as examples for objects.
They wrap our normal objects in a certain state and give them a name.
They are composed of other example-objects and immutable value-objects and can be stored and retrieved.
They can be stored, by creating factory-methods on the class-side, this factory-methods could call each other, to build up more complex example-objects.
I am not decided, if I want to use literal arrays or XML, to descibe the example-objects in the factory methods.
Any tips here?
The GUI for example-objects allows us to compose them out of existing ones or wrap value-objects, it also allows us to store and retrieve them. I imagine some nice tree-widgets and drag'n'drop-mechanism, I'll try StarBrowser, made by Roel Wuyts, who happens to sit one room next to me :-)
This leads to easy reuse and composition of "fixtures"=examples, as they are not bound to any tests like in XUnit.
- Why should we have programs at the beginning, which are much more powerful than our tests at the beginning?
- Why should we have to switch context by leaving our browser and going to the test-browser for programming?
- Why is my test-method not renamed, when I rename the according program method?
- Why can't I test encapsulated states with unit-tests?
- Why is there a duplication of class-hierarchies, one for the program and one for the unit-tests?
- Why do I have to do strange things to make sure that there is only one object for Singletons?
- Why can't I define factorial without an "if" but in a true polymorphic way? like:
0 >> factorial
Integer >> factorial
self assert: (self > 0).
^self * (self -1)
Example-methods are composed of example-objects, which serve as the receiver and the arguments of a method, and the method itself and they are executable, as there is a "running" instance for every object involved.
We can put the assertions (in principle - problems see below) directly into the example-methods, as they are valid exactly for this receiver and that parameters. See also the existing work of Ted Kaehler in Squeak: Classes MethodCall and Verifier
When I change my instance-specific code to be more general and refactor it into a "normal" instance-method,I keep the example-method, which provides me with examples and assertions.
The "normal" instance-method is then called from the example-method via "self class", because that is what I do here: I abstract the behavior from _this one_ object to _all_ objects of this class.
In detail the method-lookup would look like this:
- Look first for the method, which exactly uses this receiver and exactly that parameters. Here a precalculated object could be returned and the side-effects easily similar executed.
- then do the same by cutting all the parameters from right to left.
- then go up the super chain.
So in principle a change of the lookup in the VM would be neccessary, as it has been discussed on the Squeak-dev-list recently:
I think, that I stick with some MethodWrapper-Tricks at the beginning to achieve that changed lookup. The plan is to wrap the methods, which have example-methods or are example-methods only, and then, by intercepting their evaluation, delegate the execution to the most concrete implementation of this method, which is stored in that wrapper. Not knowing a better place, I would suggest to store factories for example-methods also on their respective receiver-classes. Better alternatives somebody?
By having assertions in example-methods with the same name as the refactored instance-methods, I don't have to rename test-methods after I renamed the program-methods. General some refactoring of tests and methods under test should be easier or even possible in Habitat.
Maybe more important, example-methods bridge the mental gap between more abstract but not executable assertions like pre-/postconditions found in Eiffel, and concrete and executable assertions like XUnit:
I don't have to leave my browser to create tests anymore, neither if they are concrete or abstract.
Store / SqueakMap
It would be good, if we could store both example-objects and methods (or its factories) on an external Server like Store or SqueakMap, so that we could collaborate on creating examples. It might also give us some synergies, by sharing example-objects /-methods for the baseclasses of VisualWorks and Squeak.
It has to be seen, how this could be coordinated with some Maps/Packages, which are dedicated to testing.
Examples should have their own tab.
- I want to name the examples, so that I can easily identify them.
- I want to store the example-objects in factory-methods on the class side of a browser
- I want to compose example-objects out of other example-objects.
- StarBrowser-GUI would be nice, where I could select a class on the left, all examples of this class are opened as children, on the right, I can choose/add/rename instance-variables and dragndrop existing examples into the variables, the newly created example can be named then.
- I want to compose example-methods out of example-objects and write a specific method for it.
- I want to refactor code from example-methods to "normal" methods, and be able to call my more abstract methods via "class"
- I want to have concrete assertions in the example-methods to make tests superflous.
- I want to be able to execute all example-methos at a time.
- I want a tool which helps me to abstract from concrete assertions to more abstract ones (which prooves the validity of the assertion-abstarction ie. that I don't loose information when I get rid of the concrete assertion.)
- I want a VM, which supports me there (see below)
- Tests are nothing but example-methods which have assertions inside, so I like to refactor existing tests into example-methods.
Swiss National Science Foundation :-)