15
Feb 13

Uncle Bob Martin @ Objektforum Karlsruhe

On Feb 6, Robert C. Martin (“Uncle Bob”) gave a talk on “Automated Acceptance Testing: Expressing Requirements as Tests” @ ObjektForum Karlsruhe.

A few statements I found worthwhile for future reference:

  • Deployment is a business decision
  • Agile is not about getting more done, it’s about getting bad news early
  • Don’t let the tool dominate you
  • Not a lot of fixture code to write (to enable automated acceptance testing)
  • Test one thing only once

Here’s a thought which struck me while Uncle Bob waxed eloquently about Fitnesse and showed the DSL he created for testing Fitnesse:

A textual representation of a program’s or component’s output is the major prerequisite for automated acceptance testability. If you struggle with automated acceptance tests – as we do (CAD system with rich user interactions and complex objects being generated, dealing with complex object lifetime issues – “this can’t be tested automatically”) – focus on generating a simple textual representation of the results & interactions etc. This is the foundation of automated acceptance testability. It will be way easier to think about automated acceptance testing once you have this foundation in place.

Thanks to Robert for giving an energized talk despite being attacked by a flu virus and to andrena objects ag for organizing the ObjektForum event series.


27
Apr 12

The main benefits of TDD

For me, there are two main, long-term benefits of TDD (Test-Driven Development):

First, by using TDD (thus writing the tests first) you make sure that your code remains in a testable state all the time (even if you don’t write all the tests to cover all the edge cases) – As anybody working with legacy code can attest, adding unit tests to an existing code base is usually excruciatingly hard and takes a lot of time. It can be done – witness Working with Legacy Code, sure, but it’s a very long and thorny journey. You don’t want to go down this road with the new code you’re writing every day.

Second, by using TDD you always have a decent, highly decoupled design – not a perfect or insanely great design, but quite a usable design to go forward. More often than not, it will be a better design than you’re used to.

“Test-Driven Development” is actually “Test-Driven Design”.


17
Apr 12

Where does “thinking” and “design” fit into the TDD picture?

I very much enjoyed @unclebobmartin‘s generous talk at the Java User Group Karlsruhe last night. In addition to covering rockets, linear accelerators and small nuclear bombs as a means to overcome the gravitational field of the earth, he gave a great introductory talk on TDD and professionalism. Always a pleasure to be reminded on why we are doing TDD in the first place.

One of the questions which came up during Q&A – I’m paraphrasing here – was:

“I tried TDD and was told not to design or think, just to write the tests and the code. The result was a mess”.

Bob Martin’s answer was along these lines:

If you’re a TDD newbie, you need to leave your old rationale of thinking things through upfront behind. So the recommendation is “Don’t think about the design or what’s next”, but to methodically follow the mantra “Write some failing test” – “Write some code just so the test is satisfied” – “Write some failing test” – “Write some code just so the test is satisfied” – …. As your start to grok the rhythm of TDD, you gradually let your design skills in.

I would like to humbly elaborate on this answer. The practice of TDD consists of three simple steps:

  • Step 1 – “Red” – Write some failing test
  • Step 2 – “Green” – Write some code just so the test is satisfied (implement the simplest thing that could possibly work (not sure who coined the term, probably Ward Cunningham))
  • Step 3 – “Refactor” – Remove all duplication from both your production and your test code

Resist your urge to design upfront, to think through algorithmic solutions and alternatives. Just write a failing test. Then, write some code to make the test pass. Don’t write more code. Just the amount of code to make the test pass.

Now, turn to step 3. If you skip it, you will create a mess.

Step 3 is where your design skills are more than welcome. Apply your design skills to ruthlessly eliminate the duplication you created (and you did create some duplication because you did “the simplest thing that could possibly work” in step 2, didn’t you?) – Just keep in mind that you are not supposed to create a design which anticipates future requirement changes, you are supposed to ruthlessly eliminate duplication to create “the simplest design that could possibly work” for your current code base.

So there are a lot of design skills involved in TDD. It’s just that most of the design skills are applied “after the fact”. They are applied after you made the failing test pass.

There’s another place for design skills in TDD. Look at the first step – “Red”. You are supposed to write a failing test. You will probably need to instantiate an object (which, keep in mind, doesn’t exist yet). Or call a function or method (which don’t exist yet, either) on an existing object. You can apply your API design skills right there: What’s the best way to name the class or function? Which parameters do you need? How do you minimize the number of parameters? How do you make it easy for the test (and future users of the class / function) to accomplish the task at hand?

You aren’t convinced yet? You still need more places to apply your superior brain powers? Good news, there’s even more “thinking” involved. In step 1, you are supposed to write a failing test. This requires a lot of thinking:

  • What do I test next?
  • What’s the next logical step in the evolution of my method / class?
  • What are the edge cases I need to test for?
  • Which test will small enough in scope that I can cope with the implementation right away?
  • Which test will bring me closer to the solution of the task i’m working on?

Conclusion

If you follow TDD and apply your thinking and design skills as described above, you will end up with a testable, highly decoupled design. It’s not a great design yet, but chances are it’ll be a much better and more usable design than the ones you’ve encountered so far.

Addendum

You may wonder why I didn’t mention the long and hard thoughts you need to think in step 2 while writing the code to make the test pass. Two questions for you: You did write some failing test, just enough testing code so the test fails, correct? Then you did the simplest thing that could possibly work to make the test pass, correct? Nothing more? Good. Go figure.


10
Oct 11

Objektforum Karlsruhe – “Trotz Nebenläufigkeit erfolgreich Unit Tests schreiben”

Ein wirklich interessanter Vortrag mit einer nur auf den ersten Blick überraschenden Erkenntnis:

Wie erstellt man Unit Tests für nebenläufige Funktionen? Man testet nicht die Nebenläufigkeit. Man abstrahiert von der Nebenläufigkeit, das heißt man erstellt das System so, daß nebenläufige Funktionen über abstrakte Interfaces beschrieben werden.

Die nachfolgende Diskussion zeigte wieder einmal, daß der Informatiker an sich nicht einer der Flexibelsten oder Lernfähigsten ist (*). Anmerkungen wie: “Damit testen Sie aber nicht die Aspekte X und Y”, “Das ist bei uns so nicht einsetzbar, weil X” oder “Bei uns liegt aber Z vor, daher können wir nicht…” zeigten, das einige Kollegen wieder exakt die Argumente aufwärmten, die sie in den letzten 10 Jahren bereits (erfolglos) gegen die Konzepte beim Unit-Testing von Benutzerschnittstellen, Datenbankzugriffen, Netzwerkdiensten und anderem vorgebracht hatten:

Wie erstellt man Unit Tests für Benutzerschnittstellen?

Man testet nicht die Benutzerschnittstelle. Man abstrahiert von der eigentlichen Benutzerschnittstelle, indem man beispielsweise MVP (Model View Presenter) nutzt.

Wie erstellt man Unit Tests für die Datenbank?

Man testet nicht die Datenbank. Man abstrahiert von den eigentlichen Datenbankzugriffen, indem man diese in eine eigene, möglichst einfache Mapping-Schicht auslagert.

Wie erstellt man Unit Tests für Netzwerkdienste?

Man testet nicht die Netzwerkdienste. Man abstrahiert von den Netzwerkdiensten, indem man diese durch abstrakte Interfaces beschreibt.

Um zwei weitere Beispiele aus der täglichen Praxis der extragroup GmbH zu bringen:

Wie erstellt man Unit Tests für das Erstellen von Objekten in einem CAD-System?

Man testet nicht das Erstellen der Objekte. Man abstrahiert vom Erstellen der CAD-Objekte, indem das eigentliche Erstellen der CAD-Objekte in eine möglichst einfache Klasse auslagert – die man im schlimmsten Falle über ein abstraktes Interface beschreibt.

Wie erstellt man Unit Tests für das Erstellen von Werkzeugen in einem CAD-System?

Man testet nicht das Verhalten der Werkzeuge. Man abstrahiert vom den CAD-Werkzeugen, indem die eigentliche Schnittstelle zum API des CAD-System durch ein abstraktes Interface beschreibt.

Der geneigte Leser wird sicher inzwischen ein Muster erkannt haben.

Die entscheidende Erkenntnis (für uns wie für die oben genannten Kollegen) ist, daß all dies nur mit testgetriebener Entwicklung sauber möglich ist. Unit Tests nachträglich zu einem System hinzuzufügen, welches nicht auf solche Tests ausgelegt ist, erweist sich in der Praxis als enorm aufwendig.

(*) Ich zähle mich selbstverständlich auch zu genau dieser Gruppe, die neuen Ideen mit einem gezielten “Das funktioniert nicht, weil….” begegnet.


03
Oct 11

Developing a Vectorworks 2011 tool plug-in, TDD-style – Episode 4

Now that we’re able to create real Vectorworks geometry with our tool, what’s next? The “spec” laid out in Episode 0 calls for creating a red or green line depending on where the user sets the clicks. Tackling the click’s location test first sounds like an interesting challenge.

What’s the simplest way to start? If the user doesn’t click on a polygon object, our tool should ignore the clicks. How does RedGreenLineTool know if the clicks happen on a polygon object? For know, we let it know by calling MouseOverPolygonObject() before adding tool points. Sounds reasonable.

TEST_N(ToolPointsShouldBeReflectedAsStartEndPointOfLineObject)
{
	MockRedGreenLineTool vwTool;
	vwTool.MouseOverPolygonObject();
	
	vwTool.AddPoint(WorldPt3(0, 0, 0));
	vwTool.AddPoint(WorldPt3(50, 0, 0));

	CHECK_EQUAL(2, vwTool.GetNumToolPoints());
}

If we don’t call MouseOverPolygonObject() before adding points, they shouldn’t be added to the list of points tracked.

TEST_N(ToolPointNotOnPolygonShouldBeIgnored)
{
	MockRedGreenLineTool vwTool;
	
	vwTool.AddPoint(WorldPt3(0, 0, 0));
	vwTool.AddPoint(WorldPt3(50, 0, 0));
	
	CHECK_EQUAL(0, vwTool.GetNumToolPoints());
}

Implementation, of course, is rather trivial (for now ;-). We just add a bool fMockMouseOverPolygonObject to MockRedGreenLineTool, set it MouseOverPolygonObject(), test it in AddPoint() and we’re done.

Well, not really. We do have an implementation for MockRedGreenLineTool only, we need to move the implementation up the inheritance tree to RedGreenLineTool to turn it into something useful.

class RedGreenLineTool {

	virtual bool IsMouseOverPolygonObject() = 0;

	void AddPoint(const WorldPt3& p) {
		if (IsMouseOverPolygonObject())
			fToolPoints.PushData(p);
	}
};

Implementation of IsMouseOverPolygonObject() is rather trivial for MockRedGreenLineTool, we just return fMockMouseOverPolygonObject. Of course, VectorworksRedGreenLineTool needs a more sophisticated implementation, but (as intended) a rather simple one:

class VectorworksRedGreenLineTool {

	virtual bool IsMouseOverPolygonObject() {
		MCObjectHandle overObject = NULL;
		short overPart;
		long code;
		gSDK->TrackTool(overObject, overPart, code);
	
		return (overObject && ! VWPolygon2DObj::IsPolygon2DObject(overObject));
	}
};

Now this rather straightforward implementation doesn’t quite work yet, as this code doesn’t reflect the state Vectorworks is in. every mouse click is registered by Vectorworks first, then an event is sent to the tool to process it. If we choose to ignore the mouse click, we need to notify Vectorworks of this fact. Thus, we need to make some minor changes to the code to accommodate for this behavior.

class RedGreenLineTool {

	virtual void PopLastToolPoint() = 0;

	void AddPoint(const WorldPt3& p) {
		if (IsMouseOverPolygonObject())
			fToolPoints.PushData(p);
		else
			PopLastToolPoint();
	}
};

While MockRedGreenLineTool gets away with an empty implementation for PopLastToolPoint(), VectorworksRedGreenLineTool needs to notify Vectorworks that the last tool point registered should be removed:

class VectorworksRedGreenLineTool {

	virtual void PopLastToolPoint() {
		gSDK->PopLastToolPt();
	}
};

Where are we at? We do have a basic implementation of mouse tracking in place. RedGreenLineTool is able to tell if the mouse is over a polygon object while the mouse is clicked. In the next episode, we will tackle tracking mouse clicks on a single or two different polygons in order to create red or green lines.

Previous Episode


22
Jul 11

Developing a Vectorworks 2011 tool plug-in, TDD-style – Episode 3

With quite a sound class structure in place (and after returning from a refreshing summer vacation in Turkey), let’s see if we can put our new class strcuture to good use while trying to create the actual Vectorworks geometry (a line), test first – of course. Reading through the first series on TDD with Vectorworks, we used a model object for representing the geometric properties of the object and a rather thin layer to create the actual Vectorworks geometry from the model. How about trying this approach with a Vectorworks tool plug-in, too?

TEST_N(ToolPointsShouldBeReflectedAsStartEndPointOfLineObject)
{
	MockRedGreenLineTool vwTool;

	vwTool.AddPoint(WorldPt3(0, 0, 0));
	vwTool.AddPoint(WorldPt3(50, 0, 0));

	MockRedGreenLineObject* vwObject = vwTool.Create();
	
	CHECK_EQUAL(WorldPt3(0, 0, 0), vwObject->GetStartPoint());
	CHECK_EQUAL(WorldPt3(50, 0, 0), vwObject->GetEndPoint());
	
	delete vwObject;
}

We expect that the tool object will be able to create the line object for us and that the tool will take care of making sure that the start & end point of the line object will reflect the tool points.

In order to get the test to compile, we need to implement Create() for MockRedGreenLineTool.

class MockRedGreenLineTool : public RedGreenLineTool {

public:

	MockRedGreenLineObject* Create() const {
		MockRedGreenLineObject* lineObject = new MockRedGreenLineObject();

		lineObject->SetStartPoint(fToolPoints[0].fPoint);
		lineObject->SetEndPoint(fToolPoints[1].fPoint);

		return lineObject;
	}		

};

Now we need to come up with a simple implementation for MockRedGreenLineObject. Not a lot to see here – just a mock implementation.

class MockRedGreenLineObject {

public:
	
	void SetStartPoint(const WorldPt3& p) {
		fStartPoint = p;
	}
	
	void SetEndPoint(const WorldPt3& p) {
		fEndPoint = p;
	}
	
	virtual WorldPt3 GetStartPoint() const {
		return fStartPoint;
	}
	
	virtual WorldPt3 GetEndPoint() const {
		return fEndPoint;
	}
	
private:
	
	WorldPt3 fStartPoint;
	WorldPt3 fEndPoint;
};

Now the code compiles, links and the test passes. As in episode 1, the code is stuck in the “Mock” classes, but we need to drive it from Vectorworks. Let’s refactor us out of this mess.

First, we push the implementation of Create() up to the base class, RedGreenLineTool. However, RedGreenLineTool::Create() shouldn’t return an instance of MockRedGreenLineObject, but an instance of a more generic RedGreenLineObject class in order to accommodate creation of a Vectorworks-specific subclass. Creation of the particular instance is left to the subclasses of RedGreenLineTool.

We move the code from MockRedGreenLineObject to a new superclass RedGreenLineObject.

class RedGreenLineObject {

public:
	
	void SetStartPoint(const WorldPt3& p) {
		fStartPoint = p;
	}
	
	void SetEndPoint(const WorldPt3& p) {
		fEndPoint = p;
	}
	
	virtual WorldPt3 GetStartPoint() const {
		return fStartPoint;
	}
	
	virtual WorldPt3 GetEndPoint() const {
		return fEndPoint;
	}

private:
	
	WorldPt3 fStartPoint;
	WorldPt3 fEndPoint;
};

Code still compiles, links and passes the tests. Now for the surgery concerning RedGreenLineTool::Create():

class VectorworksRedGreenLineTool : public RedGreenLineTool {

	RedGreenLineObject* CreateRedGreenLineObject() const {
		return VectorworksRedGreenLineObject();
	}
};


class MockRedGreenLineTool : public RedGreenLineTool {

	RedGreenLineObject* CreateRedGreenLineObject() const {
		return MockRedGreenLineObject();
	}
};


class RedGreenLineTool {

public:

	RedGreenLineObject* CreateRedGreenLineObject() const = 0;

	RedGreenLineObject* Create() const {
		RedGreenLineObject* lineObject = CreateRedGreenLineObject();

		lineObject->SetStartPoint(fToolPoints[0].fPoint);
		lineObject->SetEndPoint(fToolPoints[1].fPoint);

		return lineObject;
	}		

};

Again, code compiles, links and our tests pass. What’s left? We need to hook up the classes to CTool_EventSink and create the Vectorworks geometry.

long CTool_EventSink::LegacyMain(long action, long message1, long message2)
{
	long result = 0;
	
	switch (action) {

		case kToolHandleComplete:
			fRedGreenLineTool->Create();
			break;
	}
};

Compiles, links. No surprises here. We start Vectorworks, select our tool, click twice to create a line – but nothing happens….yet. Ah, we forgot to create the geometry. How to do that? Here’s the simplest way of doing it which comes to my mind:

class RedGreenLineTool {

public:

	RedGreenLineObject* Create() const {
		RedGreenLineObject* lineObject = CreateRedGreenLineObject();

		lineObject->SetStartPoint(fToolPoints[0].fPoint);
		lineObject->SetEndPoint(fToolPoints[1].fPoint);

		lineObject->CreateGeometry();

		return lineObject;
	}		

};

Of course, the implementation of CreateGeometry() in MockRedGreenLineObject will be empty, whereas VectorworksRedGreenLineObject::CreateGeometry() is somewhat beefier:

class VectorworksRedGreenLineObject {

	virtual void CreateGeometry() {
		VWLine2DObj line(fStartPoint.x, fStartPoint.y, fEndPoint.x, fEndPoint.y);
		line.AddObjectToContainer(gSDK->GetDefaultContainer());
	}

};

We can easily deal with the non-testability of CreateGeometry(), as all the logic of the line object’s “model” itself is under test. And if something goes horribly wrong in CreateGeometry(), it should be easy to figure out.

RedGreenLineObject doesn’t feature an awful lot of sophisticated logic so far, but we are not done yet. The “spec” calls for creating a red or green line depending on where the user sets the clicks. Sounds like this attribute will find a good home in the RedGreenLineObject class. Speaking of it, determining the location of the clicks test first sounds like an interesting challenge. Let’s try to tackle that one in the next episode.

Previous Episode | Next Episode


17
Jun 11

Developing a Vectorworks 2011 tool plug-in, TDD-style – Episode 2

With a basic mock implementation of the tool point collecting part of a Vectorworks tool under our belt, let’s try to connect this class to a “real” Vectorworks tool event sink.

Using the MockRedGreenLineTool from within a CTool_EventSink doesn’t sound quite right. After all it’s supposed to be a mock implementation. We need to use a real tool implementation in the CTool_EventSink. Let’s do some restructuring to accomplish that. First, create a new class RedGreenLineTool with the code from MockRedGreenLineTool.

class RedGreenLineTool {
	
public:
	
	virtual ~RedGreenLineTool() {
	}

 	void AddPoint(const WorldPt3& p) {
		fToolPoints.PushData(p);
	}	

	virtual void PopPoint() {
		fToolPoints.PopData();
	}

	short GetNumToolPoints() const {
		return fToolPoints.NumItems();
	}
	
	TSmallArray<WorldPt3> fToolPoints;

};

Now, make MockRedGreenLineTool a subclass of RedGreenLineTool:

class MockRedGreenLineTool : public RedGreenLineTool {
}

and create VectorworksRedGreenLineTool, which is also a subclass of RedGreenLineTool.

class VectorworksRedGreenLineTool : public RedGreenLineTool {
}

This sure looks better to our TDD newbie eye. We have a common implementation for both the MockRedGreenLineTool used for testing and a VectorworksRedGreenLineTool used to connect the code to Vectorworks. Running the tests shows we didn’t break anything. Now we are able to use VectorworksRedGreenLineTool in a VWToolDefaultLine_EventSink implementation:

long CTool_EventSink::LegacyMain(long action, long message1, long message2)
{
	long result = 0;
	
	switch (action) {

		case kToolDoSetup:
			result = VWToolDefaultLine_EventSink::LegacyMain(action, message1, message2);
			
			fRedGreenLineTool = new VectorworksRedGreenLineTool();
			break;
			
		case kToolDoSetDown:
			result = VWToolDefaultLine_EventSink::LegacyMain(action, message1, message2);

			if (fRedGreenLineTool)
				delete fRedGreenLineTool;
			fRedGreenLineTool = NULL;
			break;
			
		case kToolPointAdded: {

			WorldPt3 toolPoint;
			if (gSDK->GetToolPt3D(gSDK->GetNumToolPts() - 1, toolPoint))
				fRedGreenLineTool->AddPoint(toolPoint);
			break;
		}
			
		case kToolPointRemoved:
			fRedGreenLineTool->PopPoint();
			break;
			
		default:
			result = VWToolDefaultLine_EventSink::LegacyMain(action, message1, message2);
			break;

	}
	
	return result;
}

Using the debugger, we are able to verify that everything is working out as expected.

Not a lot of functional but structural code changes in this episode, but we ended up with a class hierarchy which hints at things to come. We expect to push code common between the mock and Vectorworks implementation of the tool up the class hierarchy and code specific to the mock or Vectorworks implementation of the tool in the subclasses.

Let’s see how this class hierarchy holds up in the next episode when we tackle creating a line – test first, of course.

Previous Episode | Next Episode


08
Jun 11

Developing a Vectorworks 2011 tool plug-in, TDD-style – Episode 1

In Episode 0, we set the goal for this series: To develop a Vectorworks plug-in tool which creates a green line if both clicks are on the same Vectorworks polygon and creates a red line if the clicks are on two different polygons.

We will be using a slightly modified version of CppUnitLite2, for more details on how the testing targets are integrated in the Xcode projects please refer to Episode 0 of the plug-in object series.

As in our plug-in object series, the first challenge is “How do we start”?

As our tool will require two clicks, we may get away with subclassing VWToolDefaultLine_EventSink to implement the interactive behavior of the tool. Of course, we won’t be able to use a VWToolDefaultLine_EventSink in our testing target, as the testing environment runs as a separate application. So we need to come up with our own “Vectorworks tool” class. Let’s do that and see were it leads us.

What to test first? Our tool will be collecting up to two clicks (“tool points” in Vectorworks SDK lingo). Upon initialization, there are no tool point in the collection:

#include &quot;CppUnitLite2.h&quot;

TEST_N(ToolObjectShouldHaveNoPointsUponInitialization)
{
	MockRedGreenLineTool vwTool;

	CHECK_EQUAL(0, vwTool.GetNumToolPoints());
}

Not an awful lot of sophistication here, but we sure know how to make this test pass (doing the simplest thing that could possibly work):

class MockRedGreenLineTool {
	
public:
	
	short GetNumToolPoints() const {
		return 0;
	}
	
};

Compiles, links and even passes the test. Excellent. We’re off to a great start. Now for some more, ahm, sophistication. The tool should store the tool points (to create the line later on – we have no clue yet how to do that in a test-first fashion, but let’s just follow our chosen path of rather trivial steps for a while):

#include &quot;CppUnitLite2.h&quot;

TEST_N(ToolObjectShouldRememberToolPoints)
{
	MockRedGreenLineTool vwTool;
	
	vwTool.AddPoint(WorldPt3(0, 0, 0));
	vwTool.AddPoint(WorldPt3(50, 0, 0));
	
	CHECK_EQUAL(2, vwTool.GetNumToolPoints());
}

Making this test pass is no challenge either (even for a small mind like me):

class MockRedGreenLineTool {
	
public:
	
 	void AddPoint(const WorldPt3&amp; p) {
		fToolPoints.PushData(p);
	}	

	short GetNumToolPoints() const {
		return fToolPoints.NumItems();
	}
	
	TSmallArray&lt;WorldPt3&gt; fToolPoints;

};

Now that we’re on a roll, let’s add one more “event” to our class, removing tool points. Users will be able to back out of a Vectorworks tool step by step by removing the last tool point added.

TEST_N(ToolObjectPoppingToolPoints)
{
	MockRedGreenLineTool vwTool;
	
	vwTool.AddPoint(WorldPt3(0, 0, 0));
	vwTool.AddPoint(WorldPt3(50, 0, 0));
	
	CHECK_EQUAL(2, vwTool.GetNumToolPoints());

	vwTool.PopPoint();

	CHECK_EQUAL(1, vwTool.GetNumToolPoints());

	vwTool.PopPoint();

	CHECK_EQUAL(0, vwTool.GetNumToolPoints());
}

Luckily, TSmallArray features a method to remove the last entry added, so making the test pass is straightforward:

class MockRedGreenLineTool {
	
public:

	virtual void PopPoint() {
		fToolPoints.PopData();
	}

	// ...
	
};	

Where are we at? We now have a tool class collecting tool points (with the added bonus of being able to remove those tool points later). There are three questions which come to our mind:

  • We need to somehow connect MockRedGreenLineTool to the VWToolDefaultLine_EventSink
  • MockRedGreenLineTool needs to be able to know about the clicks being on a polygon
  • MockRedGreenLineTool needs to create a “red or green line object”

Let’s try to tackle connecting MockRedGreenLineTool to VWToolDefaultLine_EventSink in the next episode.

Previous EpisodeNext Episode


02
Jun 11

Developing a Vectorworks 2011 tool plug-in, TDD-style – Episode 0

In the epilogue of “Developing a Vectorworks 2011 plug-in object, TDD style”, I hinted at some not yet coherent thoughts on how to develop a Vectorworks plug-in tool test first. Creating some Vectorworks geometry test first was reasonably straightforward: Do all calculations in a separate model class and use the pre-calculated model to generate the Vectorworks geometry.

A Vectorworks tool is a more sophisticated problem for a small mind like yours truly: It interacts with the drawing in a series of clicks, objects may be tracked while tool is running etc. How do we capture this potentially rich interaction test-first? Before we tackle these questions, let’s describe the tool, code named “Red/Green Line Tool”:

As the name implies, it will collect two clicks in the document and draw a green line if the start- and endpoint of the line (the first & second “click”) are are on a 2D polygon. If the clicks aren’t on a 2D polygon, they should be ignored. To top things off, a red line should be drawn if the startpoint is located on a different 2D-polygon than the endpoint:

Redgreen

My goal is neither to cover all bases with the tests (I intend to leave those as an exercise for the reader 😉 nor to develop a shippable, fully Vectorworks-conform tool (my day job takes care of this desire), but to focus on these three questions:

  • How do I isolate the “interactiveness” of the tool in a testable class?
  • How do I isolate & test the creation of Vectorworks geometry?
  • How do the chosen tests drive the design & architecture of the code?

Tune in next week when you’ll hear Dr. Bob wondering about how to get this thing off the ground.

Episodes so far:


02
May 11

Developing a Vectorworks 2011 Plug-in, TDD-style – Epilogue

The code

[Update: I have posted an Xcode 4.6 / Visual Studio 2010 project for Vectorworks 2013 here.]

Please download the Xcode 3.2.3 project including all the sources here – SimpleCabinet.zip. The folder SimpleCabinet should be dropped into Vectorworks 2011 SDK’s Sources folder, like this:

The project features two targets, a testing target named “CppUnitLite2” and the plug-in module shared library target, which outputs to /Applications/Vectorworks2011/Plug-ins. You will notice that all classes plus tests are contained in ExtObject.cpp. I’m doing this for small spikes – in real life, I would put each class in a separate file, no code in the header file etc.

The code features additional tests, refactorings and more usage of VWFC in SimpleCabinetCreator.

I haven’t gotten around to preparing & cleaning up the Visual Studio project of the Simple Cabinet project. Make sure to bug me so I feel obliged to release it, too.

Summary

It’s possible to create VectorWorks plug-in objects via TDD. In fact, it’s desirable: Developing the code test-first gave us a very nice separation between the testable model and a very thin layer creating the actual Vectorworks geometry. The SDK stubs (which exist, at this point, for the sole purpose of satisfying the linker) allow us to have a separate testing target and to mix testing code and non-testing code freely.

I found that it’s also possible to develop Vectorworks Layoutmanager-based dialogs (either straight ISDK or VWUI-based) in a strict TDD fashion, but that’s another series of posts waiting to be written and code / project files waiting to be cleaned up.

Wether it’s possible to develop Vectorworks tools in a test-driven fashion remains to be seen (although I’m pretty optimistic that it can be done quite nicely). I do have some thoughts on this matter but they aren’t coherent yet.

If you have any feedback regarding this series, please do not hesitate to post comments on the episodes, or contact me at hm dot kern at extragroup dot de, on Twitter or Facebook. I look forward to hearing from you.

Episodes