Standardizing tools can lead to testing “synergies”

It’s everyone’s favorite square on buzzword bingo.

The one with the word “synergies” written on it.

But just because managers and consultants overuse a word, doesn’t mean it doesn’t have real value.

I’m talking about standardizing testing tools.

I was just talking with someone about a consulting opportunity at a large company. Their challenge is that they have a bunch of cobbled together testing systems for different teams. They need to build it all under one system. The list of skills needed is miles long:

Java, Javascript, Python, Selenium, Protractor, QUnit, JUnit, TestNG, iOS, Android, Maven, Gradle, Puppet, Chef, Docker, Vagrant, VMWare, Angular, Backbone, …

My response:

It looks like the first big challenge will be standardizing tools.

There could be a lot of easy wins by picking, for example:

* Maven or Gradle for builds
* Puppet or Chef for deployments
* JUnit or TestNG for test runner

That could be quite a challenge for teams who “like what they know” and don’t see the value of change.

But if there are projects with neglected or troublesome build/deploy/test cycles, then standardization could be the price of admission into a concierge service that builds their code, runs unit tests and static analysis, deploys it to a test environment, and runs system & integration tests. The biggest challenge I’ve seen is getting permission and resources for the test environments, and it can be like pulling teeth to find out all the dependencies & data that need to be assembled (or mocked). At the end of it, though, there’s a virtual cycle that can’t be stopped.

And when you have standard tools in place and a smooth build/deploy/test in place, then teams really can start leveraging shared tools.

Synergies achieved. Without layoffs. BTW, they’re looking for additional SDETs in Seattle. Let me know if you’re interested in tackling a big project.

How to reduce the number of mocks for testing

We all know that testing objects in isolation is a good thing. (You should also test objects interacting, but that’s a different subject.)

Testing in isolation allows us to concentrate on testing a behavior without introducing additional complexity. It also allows us to strictly regulate the external state and eliminate potentially unexpected behavior. To accomplish this, we often turn to mock objects.

Mocks can do two main things:

  1. Isolate the object under test from its dependencies
  2. Isolate the object under test from external communication

Let’s say we want to make a self-driving car. We’ll start off with an existing vehicle.


car = new Car(make, model, year)

The first thing it needs is a robot driver.


car.driver = new RobotDriver()

Since the robot driver doesn’t need to take up a seat, we’ll throw him in the trunk so we can have another passenger. And we might as well remove the steering wheel, pedals, and shifter to make room for him. (Assume our robot driver will not malfunction so there is no need for a manual override.)


car.remove(steeringWheel)
car.remove(shifter)
car.remove(pedals)

Now we can get to our first test. We want to check how many passengers can fit into our car now.


assert.that(car.passengerCapacity()).equals(4)

For our purposes, the driver doesn’t count as a passenger.

We probably should have checked the capacity before ripping out all that stuff, and then we could write a more flexible test. We’ll also create a factory that can transform a regular car into a self-driving car.


car = new Car(make, model, year)
initialPassengerCapacity = car.passengerCapacity()
selfDrivingCar = RobotCarFactory.transform(car)
assert.that(selfDrivingCar.passengerCapacity()).is.greaterThan(initialPassengerCapacity)

All well and good, but we’ve done a lot of hand waving. Of course, there is no such robot car factory, and no robot driver (yet). That shouldn’t stop us from testing. And that’s where mocks come in.

What does a driver do?  First we need to define an interface.


interface driver {
startEngine()
stopEngine()
accelerate(speed)
decelerate(speed)
drive(duration)
turnRight(degrees)
turnLeft(degrees)
chatWithPassengers()
}

Now, as long as both a human driver and robot driver can do all of these things, they can satisfy the interface and be substituted for one another by our car.

But you might remember that we don’t actually have a robot driver. (Developers say it should be ready by next week, but they’ve been saying that for a long time.) So we create a mock:


robotDriver = mock(driver)

And now we can test it.


car.driver.startEngine()
car.driver.accelerate(milesPerHour(60))
car.driver.drive(minutes(1))
car.driver.turnRight(90)
car.driver.drive(minutes(1))
car.driver.decelerate(milePerHour(60))
car.stopEngine()


assert.that(car.distanceTraveled()).equals(miles(2))

You can see pretty explicitly here that a driver is a dependency of the car (if we want it to go anywhere.) In this case, our mock robot driver could just be a person with a remote control.

Our goal here is to show that our car works without a human driver and that there is room for an extra passenger. (So that we can obtain additional funding from our investors who want an edge in the ride-sharing marketplace.)

Stay tuned for part 2.