Archive for June, 2010

Blog Hop

June 25, 2010

I started this blog last November as an exercise in reflective practice, and it was my first experience in blogging. I have enjoyed this endeavor, but I have decided to move the blog for the time being. Until further notice, you can follow my writings at http://paulgestwicki.blogspot.com/. The content and themes of the blog will not significantly change — just the service provider.

Advertisements

Unit Testing Google Wave Robots

June 18, 2010

I am happy to report that I am now successfully running unit tests on my Wave robot. For this example, I will show how to configure a unit test for a Java robot that responds to the WAVELET_SELF_ADDED event, which is fired when the robot is first added to a wave. My sample robot is designed to do something very simple: when added to a wave, it records the root blip id in an AppEngine data store. Specifically, I’m using JDO to persist objects in the data store.

The configuration comprises:

  • Eclipse with the Google Plug-in. This is not strictly necessary, but you would be crazy to go without it.
  • All of the robot development libraries. The installation of these is covered in the Wave Robots API Java Tutorial. You will need to make sure that gson.jar, oauth.jar, wave-model.jar, and wave-robot-api.jar are on your build path. (I’m skipping the version numbers in the files for clarity.)
  • AppEngine libraries on the build path as described in the Local Unit Testing of the GAE documentation. Two of these were in my google plugin directory but not attached to the project: appengine-api-stubs.jar and appengine-testing.jar. These I put into a lib folder and added to the classpath. Other libraries may be in the war and not yet on the build path, such as appengine-api.jar and appengine-api-labs.jar.

My WaveletRecord class for this example is very simple:

@PersistenceCapable
public class WaveletRecord {
    @SuppressWarnings("unused")
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Key key;

    @Persistent
    private String rootBlipId;

    public String rootBlipId() { return rootBlipId; }
}

Now, we’re ready to write the unit test. Once again, the GAE/J Local Unit Testing documentation will get us started. We need to have a LocalServiceTestHelper along with setUp and tearDown methods, just like in their example:

public class RobotTest {

    private final LocalServiceTestHelper helper =
        new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig());

    @Before
    public void setUp() {
        helper.setUp();
    }

    @After
    public void tearDown() {
        helper.tearDown();
    }

I want to test how my robot responds to onWaveletSelfAdded, so I need a WaveletSelfAddedEvent object. As described in the Debugging Wave Robots article, we can use AppEngine logs to extract the JSON messages that are actually passed between the Wave client and a Robot. In my case, I have just such a String from my logs:

{"events":[{"type":"WAVELET_SELF_ADDED","modifiedBy":"paul.gestwicki@wavesandbox.com","timestamp":1276086048055,"properties":{"blipId":"b+X"}}],"wavelet":{"creationTime":1276086030870,"lastModifiedTime":1276086048055,"version":13,"participants":["paul.gestwicki@wavesandbox.com","cmarnold2@wavesandbox.com","writingthewave@appspot.com"],"participantRoles":{"paul.gestwicki@wavesandbox.com":"FULL","cmarnold2@wavesandbox.com":"FULL","writingthewave@appspot.com":"FULL"},"dataDocuments":{},"tags":[],"creator":"paul.gestwicki@wavesandbox.com","rootBlipId":"b+X","title":"Bugs squashed","waveId":"wavesandbox.com!w+JkmJGit7A","waveletId":"wavesandbox.com!conv+root"},"blips":{"b+X":{"annotations":[{"name":"conv/title","value":"","range":{"start":0,"end":14}},{"name":"lang","value":"en","range":{"start":0,"end":14}},{"name":"lang","value":"en","range":{"start":15,"end":431}}],"elements":{"0":{"type":"LINE","properties":{}},"14":{"type":"LINE","properties":{}},"15":{"type":"LINE","properties":{}}},"blipId":"b+X","childBlipIds":[],"contributors":["paul.gestwicki@wavesandbox.com"],"creator":"paul.gestwicki@wavesandbox.com","content":"Nothing to see here, please move along.","lastModifiedTime":1276086031862,"parentBlipId":null,"version":8,"waveId":"wavesandbox.com!w+JkmJGit7A","waveletId":"wavesandbox.com!conv+root"}},"robotAddress":"writingthewave@appspot.com"}

Of course, we can’t just put that into our Java code directly: we have to escape all of the quotation marks to make it a valid String literal. However, once we do that, we can make a handy constant like this:

private static final String SELF_ADDED_EVENT = "{\"events\":[{\"type\":\"WAVELET_SELF_ADDED\",\"modifiedBy\":\"paul.gestwicki@wavesandbox.com\",\"timestamp\":1276086048055,\"properties\":{\"blipId\":\"b+X\"}}],\"wavelet\":{\"creationTime\":1276086030870,\"lastModifiedTime\":1276086048055,\"version\":13,\"participants\":[\"paul.gestwicki@wavesandbox.com\",\"cmarnold2@wavesandbox.com\",\"writingthewave@appspot.com\"],\"participantRoles\":{\"paul.gestwicki@wavesandbox.com\":\"FULL\",\"cmarnold2@wavesandbox.com\":\"FULL\",\"writingthewave@appspot.com\":\"FULL\"},\"dataDocuments\":{},\"tags\":[],\"creator\":\"paul.gestwicki@wavesandbox.com\",\"rootBlipId\":\"b+X\",\"title\":\"Bugs squashed\",\"waveId\":\"wavesandbox.com!w+JkmJGit7A\",\"waveletId\":\"wavesandbox.com!conv+root\"},\"blips\":{\"b+X\":{\"annotations\":[{\"name\":\"conv/title\",\"value\":\"\",\"range\":{\"start\":0,\"end\":14}},{\"name\":\"lang\",\"value\":\"en\",\"range\":{\"start\":0,\"end\":14}},{\"name\":\"lang\",\"value\":\"en\",\"range\":{\"start\":15,\"end\":431}}],\"elements\":{\"0\":{\"type\":\"LINE\",\"properties\":{}},\"14\":{\"type\":\"LINE\",\"properties\":{}},\"15\":{\"type\":\"LINE\",\"properties\":{}}},\"blipId\":\"b+X\",\"childBlipIds\":[],\"contributors\":[\"paul.gestwicki@wavesandbox.com\"],\"creator\":\"paul.gestwicki@wavesandbox.com\",\"content\":\"Nothing to see here, please move along.\",\"lastModifiedTime\":1276086031862,\"parentBlipId\":null,\"version\":8,\"waveId\":\"wavesandbox.com!w+JkmJGit7A\",\"waveletId\":\"wavesandbox.com!conv+root\"}},\"robotAddress\":\"writingthewave@appspot.com\"}";

Yes, it’s ugly, but it allows us now to easily create a real WaveletSelfAddedEvent using Google’s gson library, which you have as part of the robot development libraries. We just need to make a GSON factory, parse the JSON, and pull out our event from the bundle. That is,

GsonFactory gsonFactory = new GsonFactory();
Gson gson = gsonFactory.create();
EventMessageBundle bundle = gson.fromJson(SELF_ADDED_EVENT, EventMessageBundle.class);
WaveletSelfAddedEvent event = (WaveletSelfAddedEvent) bundle.getEvents().get(0);

Now, unlike in the GAE/J Local Unit Testing approach, I am going to use JDO to check that my robot is behaving as expected. Will this work? Honestly, it surprised me, but it sure does: configuring the LocalServiceTestHelper was enough to allow us to get an appropriate PersistenceManager using the usual techniques.

The following code will create for us a query that will return all WaveletRecord objects:

PersistenceManager pm = PMF.get().getPersistenceManager();
Query query = pm.newQuery(WaveletRecord.class);

We will also have a sanity check to ensure that there is nothing there yet.

List records = (List) query.execute();
assertEquals("We should start out with nothing in the datastore.", 0, records.size());

Running the robot is easy as pie once we know what we’re doing. My robot is implemented as DataCollectorServlet, so I can poke it and see how it reacts to being added to a wave.

DataCollectorServlet robot = new DataCollectorServlet();
robot.onWaveletSelfAdded(event);

How do we know if this worked? If there is now a WaveletRecord in the datastore and its root blip ID matches the one in the event. Notice that I don’t have to hardcode that root blip ID anywhere: I’ll just pull it out of the event. The event is not a “mock object”: it’s a real event as Wave would generate, but it has all the nice lightweightness of a mock object.

records = (List) pm.newQuery(WaveletRecord.class).execute();
assertEquals("There should be exactly one record returned by the query.", 1, records.size());
WaveletRecord waveletRecord = records.get(0);
assertEquals("The rootblip id of the single wavelet record should match the one in the event.",
  event.getWavelet().getRootBlipId(), waveletRecord.rootBlipId());

That’s it! The unit test passes, and now test-driven development can commence.

(Turns out my robot actually writes a blip on being added, too, but testing that is left as an exercise for the reader.)

CS1, Thinking, Programming, and Impediments to success

June 11, 2010

The following situation has arisen several times in my teaching of CS1. In an introduction to object-oriented decomposition, I demonstrate to my students how to create a MyRectangle class. This class has a constructor that takes the x, y, width, and height of a rectangle, and it has one public draw method. We’re using Java, so the draw method accepts a Graphics parameter. The draw method is very simple: g.draw(x,y,width,height), using the attributes of the rectangle. This gets us into a nice discussion of “rectangleness”, which is represented by the class, versus individual immutable rectangles, which are represented by objects. (NB: I do not follow the naive textbook approach and have every object follow whitebox design with public accessors and mutators for all attributes. That is just training students in established bad practice. I teach them to make every object immutable whenever possible.)

From here, I have them create a MyOval class. The students check out the Graphics class in the Java API and realize that there’s a drawOval method that looks deceivingly like the drawRect method, and sure enough, the implementation of MyOval is strikingly similar to MyRectangle’s.

Then, after extended discussion about encapsulation and object-oriented systems as collections of objects that communicate by passing messages, I explain the following problem: when I define a circle, I don’t like defining it by its bounding box. Unlike a rectangle or an oval, I want to define a circle by a center point and a radius. This is “natural” to me, the user of the MyCircle class. I give them this challenge, and away they go.

First, they look for a drawCircle method in the Graphics class. There is no such thing, so I point out that circles are special cases of ovals. Then, one of two things happens: either the student jumps in and writes MyCircle’s draw method to be exactly like MyOval’s, or they make some attempt to determine how a circle would be specified by its center and radius instead of a bounding box. Note that in the former case, the circle will end up in the wrong position.

I recently gave this assignment, and for the first time, none of the students got it right. Usually, it’s a minority, but at least somebody gets it. The astonishing thing this semester is that only one or two submissions showed any evidence of analysis of the problem, doing anything beyond simply calling g.drawOval(x,y,radius,radius).

The solution to the problem is, to me, so dazzlingly obvious that it’s hard to explain. It is simple geometry: if you want to get the x,y coordinates of the bounding box of a circle, you subtract the radius from the center point’s x and y. It’s geometry, easily solved visually:

the circle is specified by center x,y and radius, so the solution is quite easyAs far as I can tell, there’s only two reasons how someone could get this wrong if they were actually trying to succeed:

  1. They did not take the time to read and understand the problem before solving it. This is a common failure among CS1 students: given a problem, they start at the keyboard instead of in their brains. They start typing and typing and typing and expecting magic to happen. After all, isn’t that what the professor does in class?
    No it isn’t! For all of these drawing problems, I always start with “analytical mode”, drawing the problem out on the whiteboard, asking for suggestions, talking through my thought process, and then writing the code for it.
    This is, in my estimation, a failure of knowing how to think, i.e. a failure of reflective practice. This is the result of inculcation in regurgitative non-learning, in which points are given for showing up.
  2. They lack mathematical literacy. In this particular case, they have no grasp of geometry or the relationships between numbers. I know much less about acquisition of mathematical literacy than I do about the science of learning in general, and so the specific evidence is unclear to me. However, I have noticed that, when faced with any mathematical task — even trivial ones — many of my students will completely lock up. Of those who don’t completely freeze and wait to be handed an answer, many of these go off in crazy flights of fantasy, totally unrelated to the problem at hand.

These two factors are not independent. Although the latter may have more specific roots in the cultural establishment of math anxiety, I suggest that they may have the same root cause: students do not know how to learn. This can manifest as (1) not taking the time to read and think about a problem before answering it and (2) never needing to learn how to solve problems. This experience makes me think of Polya’s How to Solve It. I was underwhelmed reading the book because I knew most of it already, but I think this is significant: I would wager that successful students (i.e. students of life, not just people taking classes) would read that book and see it as confirmation of what they already do. On the other hand, it’s exactly the kind of thinking that many people lack, such as struggling CS1 students.

Bringing this back to the task at hand, I am forced again to look at this question: What should students learn in CS1? There are a few reasonable answers, but the two that rise to the top of the list are (1) how to think computationally and (2) how to program. The latter is dependent on the former. The former is dependent on being able to do elementary mathematics, which itself is dependent on being able to learn in the first place. If it’s true that many of my students don’t know how to learn and don’t have fundamental mathematical literacy, then it’s no wonder that CS1 courses have 50% withdraw and failure rates!

I am reminded of an uncomfortable discussion I had with a mathematics professor about programming. She was pooh-poohing the study of programming, claiming that it was easy to teach programming to her upper-level mathematics students, so therefore programming was easy. I take this as anecdotal evidence of my theories: give me a class full of students with mathematical aptitude and problem-solving skills, and I could certainly teach them the simple task of programming. They already know the hard part—thinking and problem-solving strategies—that I am challenged to teach to everyone else.

The All-Novices in the Cooperative Game

June 3, 2010

I am about halfway through Alistair Cockburn‘s Agile Software Development: The Cooperative Game, second edition. The book provides an excellent and thorough coverage of agile methodology development, which dovetails nicely with my need to develop an effective methodology for Fall’s game programming course. I have taken copious notes and written several times in the margin, “How will this apply to CS315?”

Chapter 5.1 was added in the second edition in response to the myriad misunderstandings of the agile manifesto. In this chapter is Cockburn’s first explicit consideration of dealing with an all-novice team, which is my situation in the Fall. This paragraph is from p258, in a section addressing the misconception that agile methodologies only work with expert developers.

If I had a project consisting only of novices, I would put my money on their doing better with an agile rather than a plan-driven process. I can’t imagine novices coming up with a meaningful plan from the beginning, or delivering a successful project by sticking to that plan, or updating the plan every time they found a mistake in it. An agile process would call for them to work together, integrate frequently, test their code, show their results to their sponsors and users, and update their plans and working practices accordingly. With this approach, they would have a better chance to learn and improve within the project.

I have been using agile methods to varying degrees in my project-oriented classes, and I am certain that they engender learning and improvement. I think it’s worth teachers’ time to look at the best practices espoused by the signatories of the Agile Manifesto.

This has important implications for classes, especially project-based, studio-based, or otherwise constructivist learning experiences. Perhaps the most important is that it is the students’ right and responsibility to modify the environment after each iteration.  If students are the owners of their learning experiences, then they must be held to the same level of citizenship as a programmer on a software development team. I find that there are two impediments that challenge this, namely the professors’ and the students’ comfort with conventional objectivist designs. The sooner we can break both populations’ addictions, the sooner we can get on with learning.

Further on in the same section, Cockburn tackles the problem of the ratio of novices to experts. In dealing with an excess of novices, in situations in which getting rid of them is not an option, he recommends:

Use an agile process with the smallest number of people possible to get the job done; let the rest of the people do anything they want just so long as they don’t interfere with the progress of the development team. (Emphasis in original.)

There’s a convenient solution to a production-intensive learning experience in which firing personnel is not option. Of course, these folks should not expect “A” grades.

(I plan on writing more about how Cockburn’s observations and advice can apply to the design of project-oriented courses, but this particular paragraph inspired me to start tonight.)

Elemental

June 2, 2010

One of the teams (or, I think, one of the members of one of the teams) from my Android-HCI class cleaned up the class project and has released it on the Android Market. If you have an Android device, open the market and do a search for “Elemental”, and you’ll find a nifty little periodic table application by Team Phosphoric. Well done, gentlemen! I found posting an Android app on the Market to be a fulfilling personal experience and a great learning experience as well.

The good folks at Google have promised that we should get a Web-searchable version of the Market soon, as well as cloud-to-device messaging, which will allow me to give you a link directly to the app installation. Until then, you’ll have to do it the conventional way.