Skip to main content
blog title image

9 minute read - Software Testing Test Automation

Comparing Automated Tic Tac Toe Implementations

Jun 11, 2020

TLDR; Comparing implementations is not done to criticise, it is done to learn, and understand that different motivations and end goals create different code. Different models of the application, lead to different code. The programmer implements models of the domain, and behaviour, as code.

I previously blogged about an implementation of playing PlayTicTacToe.org in JavaScript, and also mentioned Angie Jones initial implementation in Java.

In this post I am going to compare the code and implementation approach to see what we learn.

From this point out I’m not going to use names (Angie, Alan) I’m going to refer to the implementations more dispassionately as (Java, JavaScript).

Video

Angie invited me on to her weekly live stream, and we discussed the implementations (and a bunch of other stuff) in the live stream embedded below:

Time To Create

Both implementations, in terms of coding time, are less than an hour, and probably less than 30 minutes.

They were both coded as live (for Java) and semi-live (for JavaScript). This is an unnatural way to code and takes longer than normal.

Also, when ’thinking aloud’ during coding, and trying to explain what you are doing and thinking takes longer, and can lead to coding mistakes, losing train of thought etc.

So neither videos actually show what coding approach is really like. But both come in ‘fast’ which makes them good examples of Tactically automating to get something working, which can be built on later.

Both demonstrate that the chosen technologies can be used for experimenting, prototyping, and exploring.

Main Differences

When you look at different code implementations they often differ based on.

  • Requirements Implemented
  • Models Used
  • Implementation of Models

Requirements

Neither solution had formal requirements, so the requiremnts listed here are ‘aims’ derived from watching the videos and reading the blog posts.

At a high level both implementations have the same stated requirement.

But the interpretation of that requirement is very different. Which leads to very different solutions.

The interpretation of this requirement for the Java implementation has a lot of additional implicit requirements:

  • play the game, reporting information to the human observer
  • demonstrate various coding approaches to teach the viewer
  • demonstrate use of the tooling and research during the coding
  • prototype a ’tool’ that could be run as an external executable

This adds more of an application structure to the implementation.

The JavaScript implementation interpreted the requirement in a much less user or viewer friendly approach.

  • simulate playing the game

That was it. The whole point of the JavaScript solution was the end result of having the game automatically played in the browser.

The JavaScript is a much more adhoc tactical solution as a result.

Modelling

One of the biggest differences in the solutions relates to the modelling of the Application Under Test.

The JavaScript implementation:

  • ignores all concepts of the Tic Tac Toe or Game Domain
  • views the application as a simple state machine
  • treats the application as an event handler

The Java implementation:

  • models the game board
  • models the Tic Tac Toe concept of a ‘space’
  • models the concept of a ‘game’ (winner, loser, over, start)
  • models the concept of an application to Play the game
  • the application main method actually models playing the game e.g. 5 plays, etc.

The underlying models change the shape of the solution and its implementation.

The Java implementation would be potentially easier to:

  • build on to add a ‘find best position’ strategy
  • write Unit tests for

The JavaScript model focuses on ‘create something that simulates a user making the game happen’. And takes a minimal approach to it, i.e. it does the bare minimum to simuate a player.

The Java model is much more ‘programmer’ and ‘reviewer’ friendly, and is better positioned to build on.

The JavaScript model is more suited to a ’throw away’ activity. A ‘prove that this can work’ type solution. It is the quickest, most rough and ready approach.

This modelling is not based on the language chosen. Both languages could create effectively the same implementation. And it is not determined by the development tools used. It would be perfectly possible to create multiple classes in the snippets view in the browser in JavaScript.

The differentiator here is that the Modelling was driven by the aims or requirements of the solution creation exercise.

The JavaScript implementation was focuses on playing the game.

The Java implementation was also influenced by the need to explain how to write an implementation that plays the game.

Modelling Tactically

The JavaScript model is purely tactical.

It leads to an implementation that could be used on almost any game that relies on clicking on stuff randomly.

To amend to other implementations we change the locators to identify the end state and the clickable items.

It would be harder then to implement any domain assertions. e.g. did we win? did we click in the right place? did any game rules become violated during gameplay?

Since none of this is in the model, we can’t assert on it.

Modelling Domains

When we automate more strategically modelling the domain helps.

Because we can compare the model of the domain, with the actual running implementation to assert on the validity of the model. i.e. is the application ‘working’.

The Java implementation models the domain.

This allows the Java code to tell the user if they won or lost. And enables expanding the code to automatically assert on conditions in the gameplay.

Modelling the domain in code can lead to more abstractions which can protect us from change when the implementation changes. i.e. the underlying domain might not change, so some parts of our automated execution code do not have to be amended, only those parts of the code which map on to the changed domain actually change.

e.g. the game of tic-tac-toe will never change, so if we have a model of the ‘game’ then we can apply it regardless of the implementation. The parts of the code that would change relate to the implementation e.g. how does the implementation tell us that we ‘won’, or ’lost’, how do we ‘play a cell’. This can make it easier to maintain longer term.

The Java code has more resilience in the face of a changed implementation.

The JavaScript code is vulnerable to changes in the implementation requiring a lot of change in the implementation. Fortunately the code is so small that this doesn’t really matter. But if we were automating a larger or more complicated application, the lack of abstractions and models in the JavaScript implementation would make it more of a risk.

Mix

Understanding if our code is tactical or strategic allows us to review it in terms of:

  • did we go far enough?
  • did we go too far in modelling the domain when we don’t need to for the activity we are doing?
  • have we made our code more complicated than it needs to be?
  • have we created a sub-optimal implementation that will be hard to build on?

Looking at different implementations also helps us to make appropriate decisions when coding:

  • are we adding additional risk to the project due to our approach?
  • do we need to create a separation between the domain and implementation models?

Automation vs Test Automation

Both are examples of “Automation” or “Automating” rather than “Test Automation”.

The are designed to automate the system, rather than provide specific information to support testing.

They could both be used to support testing, i.e. by having a human observer monitor the application, or memory usage etc.

The Java version has created abstractions that could be re-used from @Test methods to create automated execution with assertions specific for “Testing”.

Automating can be a completely separate activity from Automating to support testing.

I prefer to think of Automating as a completely separate activity. But one which I often use in the context of a testing effort.

Too often automated execution solutions are so tied to ’testing’ that they can’t be used for anything else e.g. when the automating is heavily tied to a framework such that it can’t be re-used.

Other points

Both Implementations use different selector strategies. The Java implementation uses XPath, the JavaScript implementation uses CSS.

This is partially biases from the programmers i.e. each will have a preference.

The Java implementation has the choice of XPath or CSS, or a combination of both at different times.

When I first wrote this post I didn’t know that JavaScript supports XPath in the browser using document.evaluate. So I thought that the JavaScript implementation had no choice but to use CSS. document.evaluate was brought to my attention by Alex Riviere in a comment to the blog post.

I would still lean towards CSS because I do a lot of web development as well, and I think programmers I work with understand CSS more than XPath. But it is great to know I have that option available to me.

And this is one reason why we share what we learn, so that other people can comment and help us learn even more. Thanks Alex.

Different technology platforms can restrict our choices.

Note that the Java Implementation could use the JavaScript implementation because of the JavascriptExecutor functionality of WebDriver. This means that we could prototype with JavaScript in the browser, migrate it to Java and then build on it from there. Learning how to use technologies in combination is very important.

Errors

Because this is ‘automation’ rather than ’test automation’, any errors encountered by the JavaScript implementation will not impact the execution.

JavaScript will throw errors if timing set too fast, but it doesn’t make any difference to the execution at all. (You can see this demo’d in the live stream.)

This is because the execution is not being used to detect errors, so will ignore any errors encountered.

Compare Yourself

I encourage you to:

  • review both implementations
  • compare the coding styles with your own
    • is there anything you need to look up and learn?
    • is it different from how you would code?
  • Experiment with the implementations
    • code for both is available
    • run it, and try it for yourself
    • amend it and see what happens
    • change it to match your coding style, did the style make a difference?

Ultimately, I encourage you to create your own implementation and communicate it to others because the more implementations people see, the more we learn, and the more possibilities we have open to us.