I’ve talked about the notion of test description languages quite a bit. A lot of these discussions get into debates about being declarative versus imperative, or focusing on intent rather than implementation. All good things to consider. But such “versus” terminology tends to suggest there is a “right” and a “wrong” when often what you have is “What makes sense in your context.” And you may have to flexibly shift between different description levels. Let’s talk about this.
Or, rather, let’s talk about Isaac Newton. Newton came up with a particular law. That law was formulated because Newton thought he discovered a force called gravity. He didn’t. But we’ll get to that. Yeah, this is one of those posts. The kind where you’re going to have to bear with me; I’ll bring this around to something more traditionally test-focused as we go.
Describing the What, Not the How
One aspect of Newton’s law bothered him quite a bit. In fact, it bothered him more than it did anyone else at the time, particularly those who started to build upon his work.
The bothersome bit was that while the law described the force that one body exerts on another, it didn’t address how the force works.
The law seemed to suggest some sort of mysterious “action at a distance.” It didn’t make any sense but it did at least allow the law to be used in a consistent way.
Just to be clear why this was annoying to Newton, consider that when the Sun attracts the Earth, somehow the Earth must “know” how far it is from the Sun. If, for example, some kind of string joined the two, then the string could propagate the force. In that case, the physics of the string (elasticity, tension, etc) would determine how strong the force was. That would be great — if that string existed. But it doesn’t. Between Sun and Earth is only relatively empty space. So how does the Sun “know” how hard to pull the Earth? Or, put another way, how does the Earth “know” how hard to be pulled?
Of course, if we just settle for a purely pragmatic route, we can apply the law of gravity without worrying about a specific physical mechanism to transmit the force from one body to another. That’s pretty much what everyone, including Newton, had to do. They had the what with very little inkling of the how.
Describing the Right What
The resolution to this problem was somewhat interesting. And ended up taking a couple of centuries.
Albert Einstein eventually came along and changed some conceptions around space, time, and even matter. This was due to his “special” theory of relativity in 1905. In 1915, Einstein generalized this theory which changed some conceptions around gravity. Specifically, it resolved the question of how a force could act at a distance.
It did so by getting rid of the force.
Expand Your Descriptions
The most obvious missing ingredient from “special” relativity was gravity. In fact, that’s why it eventually came to be called “special” — as in, a special case of a more general condition.
Einstein spent years, starting in 1911, trying to incorporate Newton’s force of gravity into relativity.
The end result was general relativity, which extends the formulation of special relativity from a flat spacetime to a curved one. You can get a basic understanding of what’s involved here by reducing space down to two dimensions instead of three. In that context space becomes a plane. And special relativity describes the motion of objects in that plane.
In the absence of gravity, objects on the plane follow straight lines largely because, as Euclid taught us, a straight line is the shortest distance between two points. If you want to put gravity into the picture, just put a star “in” the plane. Now objects traveling in the plane no longer follow straight lines; instead, they orbit the star along curves, such as ellipses.
If we stick to the idea of Newtonian forces, these paths are curved because a force diverts those objects from a straight line. In general relativity, a similar effect is obtained by bending spacetime.
So suppose our star distorts the plane, creating a little “pit” in it; a sort of “gravity well” with the star at the bottom. In this context, moving objects still follow whichever path is shortest. The technical term for this path is a geodesic. Since the spacetime plane is now bent, geodesics are no longer straight lines. For example, an object could be trapped in the pit, going round and round at a fixed height. Sort of like a planet in orbit.
That right there is a “description” of space.
Do you see how the need for a force goes away in this case?
Instead of a hypothetical force that causes the object’s path to curve, Einstein substituted a spacetime that’s already curved. That curvature affects the path of a moving object. No action at a distance is needed: spacetime is curved because that’s what stars (or other big masses) do to it and orbiting bodies respond to nearby curvature.
What everyone referred to as gravity — and what Newton and just about everyone else thought of as a force — was actually the curvature of spacetime.
Thus Levels of Description
So what we have is a description (general relativity) augmenting another description (special relativity) and replacing a previous description (Newtonian force). The difference being that the new description could become operational. It was capable of expressing a what and a how — and even a large part of why.
Einstein wrote down a series of mathematical formulas, called field equations, which described how curvature affects the motion of masses, and how the mass distribution affects curvature. But a key point is that in the absence of any masses, the field equations reduced to special relativity.
And this was nice because it meant the new description incorporated some specifics of the previous description as well.
Does This Have Relevance to Testing?
I think it does. Consider the following description for a moment. (Don’t worry about if you’re “not a coder.” Just take a look at it.)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
def create_bomb(options = {}) activation_code.set(options[:activate]) if options[:activate] deactivation_code.set(options[:deactivate]) if options[:deactivate] countdown_value.set(options[:countdown]) if options[:countdown] provision_bomb.click end def enter_code(code) trigger_code.set(code) self end def activate_bomb bomb_state.click end def countdown_has_finished t1 = Time.now duration = perform_countdown t2 = Time.now expect((t2 - t1).round).to eq(duration.to_i) end def confirm_detonation expect(detonation).to be_visible end |
Now let’s consider a slightly higher description of this:
1 2 3 4 |
on(Provision).create_bomb(countdown: 5) on(Bomb).enter_code('1234').then.activate_bomb on(Bomb).check_that.countdown_has_finished on(Bomb).confirm_detonation |
Let’s surround that a bit:
1 2 3 4 5 6 7 8 9 10 11 12 |
Given(/^an activated bomb with a countdown of "([^"]*)" seconds$/) do |seconds| on(Provision).create_bomb(countdown: seconds) on(Bomb).enter_code('1234').then.activate_bomb end When(/^the countdown finishes$/) do on(Bomb).check_that.countdown_has_finished end Then(/^the bomb will detonate$/) do on(Bomb).confirm_detonation end |
And consider that from this:
Scenario: Bomb detonates after countdown Given an activated bomb with a countdown of "5" seconds When the countdown finishes Then the bomb will detonate
What we have here are levels of description: we have a graduation of moving from the what to the how, if you read the above in reverse order.
And even then you are missing some bits. Consider at the lowest level of description, you don’t have how perform_countdown
is done. If curious, it’s done this way:
1 2 3 4 5 6 7 8 |
def perform_countdown time_left = timer_value_in_seconds (time_left.to_i + 1).downto(1) do |n| puts n.to_s.yellow sleep 1 end end |
And even here you have a level of description. Notice that line puts n.to_s.yellow
. What that’s doing is allowing the countdown to be shown at the highest level of description.
And even with that part being stated, there are still bits of the description that you are not seeing here. For example, what are trigger_code
and bomb_state
referring to?
Our Artifacts Provide Levels of Description
You could even argue that the most English-style description from above was, in turn, derived from yet some other English-level description, like a requirement or a use case. Each artifact can be a crutch and the ability to reduce sources of truth is a powerful skill to have in testing.
(If curious, this example comes from a test challenge I often use for a “Project Overlord” scenario, wherein Overlord is a business that provides bombs for super-villains.)
Sticking with just the English level, consider that my full feature specification for a certain bit of functionality is shown here: Overlord Test Example – Provisioned Bomb.
Take a moment to look at those statements. Then consider whether and to what extent they could have been further aligned with requirements and/or use cases if I restructured them as such:
Feature: Successful Bomb Provisioning Scenario: Default Bomb State * a newly provisioned bomb will display as inactive by default * a newly provisioned bomb will have a countdown of "30" seconds Scenario: Bomb Actions * a provisioned bomb can be activated * a provisioned bomb can be deactivated * a provisioned bomb will detonate when the countdown finishes * a provisioned bomb will detonate after three unsuccessful deactivation attempts Scenario: Bomb States * activated bombs will show they are active * activated bombs will have a timer that is counting down * deactivated bombs will show they are inactive * deactivated bombs will have a timer that is not counting down * the countdown timer can never be modified
How do my levels of description below the English — the stuff we looked at earlier — have to change as a result of this? And if they do, does that matter? Is one level of description “better” than another? And if so, how are we making that determination?
And, further, how much alignment should there be between the levels of description to make sure we don’t have “descriptive drift.” (Remember: general relativity essentially contained special relativity which approximated Newtonian force in all cases that mattered.) For example, consider a possible English and code representation side-by-side:
Given an activated bomb with a countdown of “5” seconds | on(Provision).create_bomb(countdown: 5) on(Bomb).enter_code(‘1234’).then.activate_bomb |
When the countdown finishes | on(Bomb).check_that.countdown_has_finished |
Then the bomb will detonate | on(Bomb).confirm_detonation |
Is there benefit to having the code parts start to read almost as if they were English or, at least, a constrained subset of it?
The Challenge for Testing Craft
So we saw that general relativity showed that Newtonian physics was not the true, exact, entire description of the universe that Newton (and almost all other scientists prior to the twentieth century) believed it to be.
However, Newtonian mechanics is still used all the time. Even today. Newtonian physics is simpler than relativity. The differences between the two levels of description mainly become apparent when considering exotic phenomena (very fast speeds, very large masses) or a very large scope (such as cosmology). When we hit those situations we have to ditch Newtonian mechanics because it simply can’t explain the key observations and we end up losing more accuracy of explanation than we gain in simplicity of expression.
So there is a limit to the efficacy of descriptions depending on the scope with which you are working.
There are “good enough” levels of description and there is a point at which they break down.
A key challenge for testing, as a discipline that interacts with developers and business, is finding out what “good enough” means and where degradation starts to occur. There is a core challenge of that idea around accuracy of explanation balanced with the simplicity of expression. Being able to work with this balance, and help others do so, is part of how we, as test specialists, recognize the human element of history and help propagate information around the project singularities.