Thursday, 10 October 2013

I love that everything is parallel, the bike is behind the vehicle and the road edge extends to the full width of the poster. It looks like there's no danger, so you have to use your imagination to make sense of the caption.

I also suspect that the campaign is not really aimed at drivers.

By the way, if you can't see the picture, try a different browser! I've been moving away from bitmap images to SVG (vector graphics), to support high-resolution devices. I've updated most of my previous posts with SVG versions of the pictures. My assumption is that most people who read my blog will have a modern browser.

Monday, 15 April 2013

When you feel the need to add a comment to a piece of code it is sometimes a signal that the code is getting too complicated. It's generally better to fix the underlying design problem than write a comment to explain the mess. As soon as code starts to become complex it is very easy to descend into a negative spiral.

However, some explanatory comments can be very helpful

I have found a lot of value writing comments to explain the rationale for a design and possible future directions that I might forget or that others will find useful. I've got a brain like a sieve and I used to find that I frequently ended up rewriting code I'd previously written because I couldn't remember the direction the design was going in or how it fits together with other pieces. A real waste of time. The comments have almost completely stopped that, but they can cause a new problem: I sometimes find it hard to resist the temptation to start implementing the future-direction ideas I'm explaining in the comment.

Wednesday, 4 July 2012

Here's a demo of a working iPad-style on-off switch that I wrote in SVG and Javascript (GWT).

SVG seems to work pretty consistently across platforms – including the iPad, iPhone and all modern desktop browsers, astonishingly even IE 9. The only unfortunate exception is Android where, apparently, SVG support was actively removed to reduce the browser footprint (by a measly 1MB). You can work around it by installing Android Firefox which supports SVG though not multi-touch.

As I developed the on-off switch control, I separated out two distinct interfaces that I called: View and Look. To give them a proper namespace, without too much clutter, I nested the interfaces inside an outer interface for the OnOffSwitch something like this:

The Look is called by a Feel object that translates mouse-clicks and touches. The View is called by the application code. My SVGOnOffSwitch widget implements both Look and View interfaces and hooks itself up to a default Feel object. Testing the Feel object was easy to do by passing it a mock Look.

It's always hard coming up with good names for interfaces and objects. I thought I'd post this because I was pleased with the way the code ended up.

Tuesday, 19 June 2012

Objects are composed of objects. The pen in your hand is composed of a nib, a cartridge, and a plastic sheath. The cartridge contains ink, some kind of valve and so forth. The word "pen" is an abstraction that allows us to communicate at a higher-level and ignore all that detail.

However, when you use a dependency injection (DI) framework, like Spring, to wire objects together, everything appears to be at the same level of abstraction.

Abstraction is the key to dealing with complexity

Without being able to distinguish higher-level abstractions it's easy to become mired in low-level implementation detail. The complexity makes it harder to reason about the application and hard to maintain a clean separation of concerns.

Find clusters that can be abstracted

To work at higher levels of abstraction, we need to encapsulate a graph of lower-level objects inside a simpler façade. The GOOS authors call this "the rule of composite simpler than the sum of its parts" (p.53).

Inner objects are not peers

The encapsulated objects are inside the higher-level object. They are not external collaborators of the object. The inner objects are an implementation detail.

To construct our objects we have two basic choices: either (1) we pass inner objects in through the constructor/setters (à la Spring) or (2) we create the inner objects inside the outer object.

Injecting inner-objects breaks encapsulation

If we pass objects in, we make it hard to distinguish internals from peers and we break encapsulation because we are forced to know how the object is implemented when we construct it.

Use "new" instead

The simple alternative is to use the new operator to create the internal objects and wire them together in the outer object's constructor. If we do this consistently through our application, we don't actually need a DI framework. Objects themselves are quite capable of performing the role that a DI container would perform.

If necessary, mix the two approaches

There are occasions when having separated configuration can be useful - e.g. for supporting plugins. There is nothing stopping us from adopting a hybrid of the two approaches. We can use a DI container for those specific objects that need it.

And vice versa: if we're already using Spring, there's nothing to stop us pulling out clusters of objects from the Spring configuration and assembling them in code, one cluster at a time. It doesn't have to be an all-or-nothing transition. It can be done gently.

Saturday, 11 June 2011

I just received very sad news that Eli Goldratt died today. He had aggressive lung cancer, presumably brought on from a lifetime of pipe-smoking. I feel extremely lucky to have met him. He has influenced my life tremendously. I only wish I'd met him earlier. :-(

Friday, 29 April 2011

Code quality always seems to get worse and worse. Even when a team is actively fighting against it, complexity inevitably wins in the end. What's going on? Why is this pull towards complexity such a powerful force?

In my view, a lot of the problem stems from the wrong actions being taken, with the best of intentions in mind.

Code quality quickly spirals down

Developers don't want to introduce bugs, so they naturally take actions to avoid doing so. Making changes to working code is risky, so developers make heavy use of conditional logic ("in my particular case do this, otherwise, do the same as before") and duplication to minimise the changes to existing code.

There's a good intention behind it. Unfortunately, it makes the code increasingly difficult to work with—to understand the ramifications of a change— and subsequently increases the pressure to tread carefully and avoid making those bigger and bolder changes to the design that are really needed to keep the code clean.

Automated regression tests aren't enough to stop it

Agile teams tend to make heavy use of automated regression tests. This not only allows the team to release code frequently, but allows them to refactor it and keep the design in good shape. The tests should catch any bugs introduced when the design is reworked.

That's the theory, but, in practice, the developers don't keep the design clean. Why? Because their old "don't make unnecessary changes" habit is so deeply-ingrained. And so, the vicious circle continues.

You have to change the habits too

How do you change habits? Education, constant reminders, code reviews, management oversight, tie a knot in your handkerchief... whatever it takes because if you don't change the habits your code quality will deteriorate and your team's agility will disappear with it.

After drawing the diagrams for my last post, I'm playing with ideas for a notation for describing options. What I've come up with so far isn't very good — I'm not even very clear on what the dimensions are... I think it's roughly time along the x-axis and choices along the y-axis, though I think I may be mixing other concepts in as well — but, anyway, I thought I'd put it out in the hope that someone else might help me improve it.

Here's a real-life example from my last project (read it from left to right):

We actually followed these steps. The team in Hong Kong worked on improving the speed of the database reports, while, in London, I built an alternative report generator that wasn't as flexible, but was very fast. By release time the Hong Kong team had ironed out all the problems with the queries and optimised the databases indexes so their reports were adequately fast. In the end, we went with a mixture of the two.

I also got in trouble by trying to be too clever. I put code in to the test environment to do live cross-checking of the reports generated by the two methods. It did reveal some discrepancies. But I got in trouble because they were trying to do speed tests at the time. Communication, communication, communication...

Let me know if you have any ideas about a notation for describing options (I'm talking about real-life options, not financial options).

Thursday, 28 April 2011

You cannot predict exactly what's going to happen in the future. Having options allows you to respond quickly as different situations arise.

Option to Abandon

Lifeboats on an ocean liner are an example of the option to abandon. The same with backups of your software. With luck you'll never need them, but it's worth creating the option just in case.

Option to Backtrack

The option to backtrack is similar to the option to abandon, except that you don't abandon the direction completely, you go back to a previous "safe" point ready to try again.

If you release a new version of some software and then find it's unstable, you want to be able to go back to the previous version. Any kind of "rollback" or "undo" capability is providing the option to backtrack.

Being able to backtrack is important for learning. To experiment with a piece of software, try out a new refactoring, or test the boundaries of a technique you want to feel safe that you can go back to where you were.

Option to Choose

This option is about alternatives. To travel between cities you might take a train, but if the train drivers are on strike you'll need to find another way. By thinking about your choice of options in advance, you won't need to panic.

An example in software is where the business logic is decoupled from the data storage technology, so that users can choose between several different databases. This can create value by widening the potential market.

Option to Defer

This option is about timing. Do we have to decide now or can we decide later? The option to defer is about pushing back decision points, to allow more information to come in, or being able to put something on hold, with the possibility of returning to it later on — for example, postponing a space shuttle launch due to icy conditions.

Traditional software development methods force the development team to commit to features a long time before they are developed or deployed. Agile methods let you defer commitment (see this article by Chris Matts and Olav Maassen for a more in-depth explanation of how options-thinking relates to agile).

Option to Expand (or Contract)

This option is about resources. If a customer wants to place a huge order with you, can you accommodate it? Can you recruit quickly? Can you move resources from a less successful project to another more successful one?

Tuesday, 29 March 2011

One of the advantages of acceptance test driven development (ATDD) is that it helps a team to agree on the objectives before diving into coding. The clearer the acceptance tests, the fewer misunderstandings there are likely to be. Every misunderstanding wastes time and adds cruft to the software.

Releasing frequently gives an opportunity for feedback and correction. Small corrections early on can prevent the need for large corrections later.

1. Well-articulated acceptance tests and frequent releases:

2. Poorly articulated acceptance tests:

3. Longer times between releases exaggerate the deviations:

4. Without any vision or objectives:

This often happens in start-ups where a product idea is hazy and the team is basically chasing every hare that runs past. Eventually they might happen to catch one, but it's a pretty inefficient way to work.

Friday, 25 March 2011

I often have ideas for start-up businesses and get quite excited about them until it slowly dawns on me there are major flaws. In a bid to spot the flaws sooner, I've come up with some evaluation criteria. I thought I'd post them, in case anyone finds them interesting or has any of their own thoughts on the subject they'd like to share.

Friday, 21 January 2011

I've never liked the Agile Manifesto. I'm not blaming the authors because I know it must have been a nightmare trying to come to a consensus, and the manifesto has served a useful purpose bringing like-minded people together under a single brand. It is slightly ironic that the wording hasn't changed in 10 years, but there you go. I'm not brave enough to propose an alternative yet. In this article, I'm just going to explain where I think the logic is faulty.

Here is the wording of the manifesto:

Manifesto for Agile Software Development

We are uncovering better ways of developing software by doing it and helping others do it. Through this work we have come to value:

Individuals and interactions over processes and tools Working software over comprehensive documentation Customer collaboration over contract negotiation Responding to change over following a plan

That is, while there is value in the items on the right, we value the items on the left more.

It doesn't distinguish agile from "slapdash"

The aim of the manifesto seems to be to try and distinguish agile from waterfall. Unfortunately, agile's worst enemy isn't waterfall but the "we don't write documentation, so we're agile" brigade. And the manifesto doesn't defend agile very well against this threat. Perhaps there's something in the twelve principles but who on earth reads them? Certainly not the slapdash developers claiming to be agile.

It mixes up goals and solutions

"We value working software over comprehensive documentation". OK, but surely whatever software development method you select, working software is the ultimate goal, isn't it? Some people believe that by writing comprehensive documentation you are more likely to produce working software. That's part of their solution. But you can't go round comparing goals with solutions. It's nonsensical. Perhaps what the manifesto authors wanted to convey was that "the code is the documentation", or something along those lines, but couldn't agree to write it, so plumped for the goal as their solution. That's cheating!

The dilemmas are easy to solve

The manifesto poses dilemmas and then tries to position agile at a certain point in each trade-off continuum. But why are we compromising? If both sides have value (as the manifesto plainly says), why don't we find solutions that resolve the conflicts? Let's take each one in turn:

I don't know whether the value of documentation is to do with maintainability, improving clarity or good governance. The point I'm trying to make is that there does not have to be any compromise between those needs and agile methods.

So what is agile then?

I'm not sure. Please can you give me feedback on this post and your ideas. Leave a comment, or blog about it, or tweet me (@davidp99). Thanks.

This is my interpretation of what he said (from memory) so it may not be quite right and he's probably refined it since then.

Running the whole company in an "agile" way

Essentially FlowChain scales agile practices up to the company level. There is a single (large) development team that works from a prioritised backlog of MMFs (Minimal Marketable Features). I'm not sure who manages the backlog. I've called it a "product strategist", but that's my name for it not Bob's.

The team works on multiple value streams. Each value stream has its own operations staff.

Zooming in:

Extreme flexibility

What I like about FlowChain is the flexibility it provides. If a value stream is doing well, resources can quickly be redeployed to capitalise on its success. Alternatively, new products (value streams) can be developed. In most organisations, moving developers from one project to another is a major upheaval, and that puts pressure on the project to be a success. When you can move developers quickly to something else, it relieves the pressure and allows the company to tackle more risky (but potentially more lucrative) opportunities.

Note that in a large company, the pool of resources for developing products could be very large, and there may therefore be many MMFs in progress at the same time. Bob didn't explain how to resolve resource conflicts, sequencing etc. We may have to wait for his book...

Sunday, 9 January 2011

The Given/When/Then style of Behaviour Driven Development (BDD), favoured by Cucumber and JBehave, puts a lot of context in the examples that I claimed was unnecessary clutter, but I had quite a few comments that puzzled me until it dawned on me there's another difference in my approach compared with the Given/When/Then style.

I always state the required behaviour in a sentence or two before giving the examples. Each behaviour is described by a specification like this:

Maybe it looks like a lot of work to write, but it isn't really. The structure is standard and each part is just a sentence or two.

Whereas in the Given/When/Then approach the business rule describing the behaviour generally isn't made explicit. The reader is expected to guess the rule from the examples, so naturally they have to have more context.

Occasionally it's difficult for the domain expert to immediately state the abstract rule and easier to start with concrete examples, but once the expert has explained a few examples then we can usually begin to take an educated guess at the rules behind them—"Ah OK, so the rule is X?"—and then have a useful discussion.

But if we don't make the rule explicit we only have the concrete examples, so they have to be made much more verbose—and potentially implementation-specific—so that readers can correctly interpret them.

It is true that Given/When/Then style examples are much better at avoiding implementation lock-in than test scripting ("click this, click that..."), but I would argue that context-free examples with explicitly stated rules are even better, both in terms of avoiding lock-in and in terms of readability (how long it takes the reader to understand the behaviour expected).

Friday, 7 January 2011

For ages I treated objects as glorified data structures to be operated on by procedural code. It took me a long time to get out of the procedural mindset and into an object-oriented mindset.

Well-written object-oriented code helps you deal with complexity by hiding internal details so that you can operate at ever higher levels of abstraction. Object-oriented code increases flexibility – objects can be plugged together in different ways like Lego™ pieces – and it keeps duplication and churn to a minimum by keeping data and operations in the same place instead of spreading them throughout the code-base.

The recent discussion on the GOOS mailing list has made me realise that others are also struggling with the transition. Nat Pryce has posted a couple of blog entries today, and I'd like to pick up the same theme with an example in the hope it might help.

Let's start with a simple PopGroup class that implements the role of SongPerformer (in Java this would be an interface):

To construct a valid PopGroup you need a Singer, a Drummer and a Keyboardist. These are the PopGroup's dependencies.

The responsibility for coordinating the various activities to perform songs is the responsibility of the PopGroup object, but the actual acts of singing, drumming and keyboard playing are separated out. This is the single responsibility principle combined with dependency injection.

This approach makes the design highly flexible because we can plug together all kinds of variations. As long as the dependencies we inject implement the right interfaces we can make the pop group perform a song.

For example:

If we don't have a DrumMachine, we can substitute a PhysicalDrummer and the PopGroup will still function the same.

Our PhysicalDrummer object depends on interfaces not implementations. If we're desperate we could grab a passer by, a dustbin and a broom handle and create a drummer:

Unfortunately the BroomHandle class does not implement the Drumstick interface. We could make it implement Drumstick, but the BroomHandle is in a different domain. It's in the domain of sweeping, rather than musical performances. We don't really want to couple its class to an interface from the musical performances domain, so we create an adapter to map between the two. This is known as a ports and adapters architecture.

To make things easier to understand we can wrap the objects into a class that has a name that better expresses the intent.

Now we've hidden all the complexity and we're operating at a higher-level of abstraction.

In reality, a ports and adapters architecture is not so much mapping between entirely unconnected domains as mapping lower-level domains to higher-level domains, so that the technical detail is hidden within simpler more abstract concepts.

Another valuable extension inserts a little icon into the output and when you hover over it, it shows logging output that occurred during the test. This is a neat solution to a dilemma that I've sometimes encountered: "If I don't write down the steps, how can I be sure the test has been implemented right?"

We want to write high-level tests that express the business requirements without reference to a particular implementation, so we can change the implementation later. To do this, we hide the test's implementation steps in its accompanying Java fixture. But some people aren't comfortable navigating and reading Java code. If you're a non-developer tester trying to make sure the system is well-tested, it can be scary to "trust" the developers to implement your tests correctly.

The temptation is therefore to explicitly write into the test specification the steps required to execute the test. The trouble is by encoding the "how" you end up locking the test into a specific implementation. The lock-in problem is exacerbated by duplication across tests. When you're detailing all the steps, you find that many tests require a similar set of steps.

Some testing frameworks, such as Robot and FitNesse, allow you to pull out common set-up but this is a compromise. Effectively what you're doing is programming, but not in a proper programming language. The intentions of your tests become lost amongst increasingly complex interweaved test scripts.

Nigel's solution allows you to write the tests in a way that retains a clear separation between intent and implementation, yet allows non-developers to be reassured that the test has been implemented correctly.

Friday, 3 December 2010

Unfortunately, I joined too late to be actively involved in organising the next SPA conference (London, June 12-15th 2011). That responsibility lies with Ivan Moore and Mike Hill as conference chairs and Willem van den Ende and Rob Bowley as programme chairs. However, I'm definitely going to attend the conference having just snapped up an early-bird ticket.

What I like about the conference is that it's not oriented around a particular technology or approach, and the attendees tend to be at the expert end of the scale. In my opinion, it's the best conference having your thoughts challenged. That's what I want from a conference.

Thursday, 2 September 2010

I've come to the view that Given/When/Then is a poor way to think about scenarios, and is even worse for writing them. It makes me turn even the simplest situations into a ramble. And I don't think it's just me.

Given a user called Fred And user Fred is logged in And user Fred has an empty shopping basket When Fred adds a $15 screw-driver And Fred adds a $12 lamp And Fred adds a $12 lamp Then the total of Fred's bill is $39

If I start with Given/When/Then, I end up having to go through a laborious process of simplifying, getting rid of unnecessary details. In our example, above, Fred may be needed behind the scenes but doesn't add anything useful to the description.

Given we are logged in And we have an empty shopping basket When we add a $15 screw-driver And add a $12 lamp And add a $12 lamp Then the total bill is $39

Do we need that bit about being logged in? No. Readers will likely assume it and, even if they don't, who cares anyway? It's irrelevant to what I'm trying to explain.

My rule of thumb is: if the context is unexpected (e.g. we're trying to explain what happens when you aren't logged in) then we need to mention it, but if the context can be inferred from the other parts of the scenario, or from common sense, then leave it out. There's a feeling that adding context will remove ambiguity, but all it actually does is complicate the example.

Given we have an empty shopping basket When we add a $15 screw-driver And add a $12 lamp And add a $12 lamp Then the total bill is $39

Are the words "screw-driver" and "lamp" necessary? I think they are. If we removed those words and just had the prices, the example would be harder to understand. You need something to show that the prices represent items. Using more abstract labels (e.g. "item A", "item B") is a possibility, but doesn't seem to buy us much.

What about the "empty shopping basket"? Can that be reasonably assumed? Yes, but rather than starting with an empty shopping basket, how about starting with a full one and dropping the "when" part?

Given a basket containing a $15 screw-driver And a $12 lamp And a $12 lamp Then the total bill is $39

Finally let's remove the duplication.

Given a basket containing a $15 screw-driver And 2 x $12 lamp Then the total bill is $39

OK, this doesn't suck as much as what I started with.

But why not just write this:

1 x $15 screw-driver 2 x $12 lamp

Total bill = $39

This is what I might write on the back of an envelope if I were giving an example to somebody. With the right tools (e.g. Concordion), I can code it up exactly as I just wrote it. I don't need to use Given/When/Then. I used to think it was a good way to structure the example, but I've changed my mind.

Tuesday, 2 March 2010

Almost every programming book I've read has irritated me. I get irritated easily (I don't get enough sleep), but Java books are the worst because Java's the language I use most and know best. I still read them because there are usually a few good ideas buried inside, but it irritates me that I have to wade through so much tosh.

So far, Growing Object-Oriented Software, Guided by Tests is the only exception. There's no tosh. It's incredible. I've read the book two or three times and each time I felt myself becoming a better programmer. If you haven't read it yet, I can't recommend it enough.

Monday, 25 May 2009

Concordion 1.3.1 was released last month with a handful of new and noteworthy features. There was an announcement on the Yahoo mailing list, but I wanted to wait for the Concordion.NET port to catch up before making an announcement on this blog.

So, if you're using Cucumber in Ruby, or FitNesse in .NET check out the alternative. No other acceptance testing framework lets you create such readable acceptance tests as Concordion's. The key advantage in having readable tests is that when you come back in three months time and wonder what the heck you were doing, you might actually have a clue.

I imagine the designers wanted all the answers to be true for the normal "happy" situation. But my brain couldn't cope with all the negatives, so I ended up writing a second set of positively-named methods to call from the negative ones. :-)

Tuesday, 14 October 2008

This is quite an obvious little pattern, but I think it's still worth documenting.

Some repetition is harmlessWhen I'm writing an example of a required behaviour, I find that I often have to refer to something I've already mentioned. For example:

Given the following names: Charles, Naomi, Melissa, ArnoldSearching for 'ar' should find: Charles, Arnold

Here, I have mentioned the names Charles and Arnold twice: once when describing the context and once when describing the expected outcome. This isn't a problem because I purposely kept the names short to make the example easy to follow.

But repetition can become awkwardIf the values are long or complicated then the examples can become hard to read and maintain. For example:

I call them labels or pseudo-identifiers to distinguish them from real identifiers in the system under test. For example, in the implementation we might have a database table called Address with an identifier column called addressId. But the identifiers we use in the examples are merely a way of making the example easier to read. They are not a reflection of any particular database model. Any mapping between real identifiers and pseudo-identifiers must always be handled behind the scenes in the fixture code.

The labels don't have to be numeric. They could be letters (A, B, C) or other more meaningful short strings.

Labels can also help you to hide detailsAnother advantage of labels is that they can let you gloss over unnecessary details. For example:

Using labels for bookings (1, 2) and for booking references (A, B) means we don't need to go into any details of what we're booking or what the booking references look like. If the format of the booking references is important, we can cover that in another test, but, in this test, we're not locking ourselves into any particular format. All we're doing is demonstrating the rule: "Each booking is given a unique booking reference that can be used to look-up the booking."

Having this extra level of indirection may make the fixture code work harder, but that is not a problem. It is much better to put complexity and implementation-related assumptions in the fixture code, where we have powerful refactoring tools at our disposal, than to complicate the specification and make it more fragile.

Maven IntegrationSeveral people requested that the Java version of Concordion be added to Maven repositories and thanks to José Manuel Beas, Wang Yi Zhou, Craig Walls, and others, this is now done (instructions for usage with Maven are here). Concordion 1.3.0 has the same functionality as 1.2.0 but with Maven support.

Thursday, 13 March 2008

I like Keith Braithwaite's "software gauges" metaphor. As he explains, a gauge is a shortcut for deciding whether something passes or fails some criteria. For example, if your bag fits inside the metal cage at an airport then it can be taken onboard as hand luggage. You don't need to use a tape measure. What's interesting about gauges is that you don't necessarily need to be able to articulate "the rules"; if you need to know the rules you can infer them from the gauge.

However, I'm not convinced with what Keith says about trader spreadsheets making good gauges. Obviously he's had success with them, so they definitely can be made to work, but would they have worked even better another way? Basically what it boils down to is that I like tables; I just don't like big hairy tables!

If the hand-luggage cage can be taken as an example of good practice then some of the properties of a good gauge appear to be: sturdy and reliable; correct enough for all practical purposes; obvious; unambiguous; quick to use; and simple to understand by the gauge user.

The spreadsheets Keith showed me contained a large number of sparsely populated columns of denormalised data with lots of magic numbers and magic strings. This doesn't seem to stack up well against the list of desirable traits for a gauge. They're not designed with the gauge user in mind. If your domain model doesn't fit the gauge, is it because the model is wrong? Or the gauge is wrong? Or your interpretation of the gauge is wrong? With so many moving parts, it's got to be difficult to work out, even with help from the traders.

Isn't it better to treat the spreadsheets as a starting point rather than an ending point? And then have someone skilled at analysis and abstraction work with the traders and extract smaller, more practical gauges. There's nothing to stop you holding these gauges in spreadsheets too. I have a lot of respect for the customer, but I don't see why, just because the traders have written a spreadsheet, you can't help them improve it. They've got their skills you've got yours.

Sunday, 9 March 2008

Most of the examples on the Concordion website are technical in nature, so I've put together a short business-focused example. Please take a look.

I'm not trying to push Concordion on you. You can do something similar with other test frameworks. It's really the approach that I'm trying to get across: focusing the acceptance tests on goals, not solutions; and decomposing behaviours, keeping each test as isolated and simple as you can.

The user-interface (UI) of an application is a solution, not a goal. I often see people writing test scripts in terms of direct user interface interactions (e.g. using a record/playback/verify tool like Selenium) unaware that by doing so they're locking themselves into a particular design.

If, instead, they hid the scripting behind goal-oriented acceptance tests they would leave themselves a lot more freedom to change the solution. I guess not all teams need that kind of freedom. But I wonder how many even know there's a choice?