Wow folks, its been a long time! I feel like I am an old friend returning from traveling on a long epic adventure. Just call me Bilbo, although sadly I would make for a very poor hobbit given I am about as tall as Kobe Bryant and am missing the trademark foot hair. Anyway, my apologizes for the long absence from posting. The good news, however, is that I feel like I am returning from my adventures with many great and wonderful stories to tell! Many of these stories will be related to the specific epic adventure that is the new R2018a release of MATLAB. Check out the release notes, it's a beast! There were huge updates to the live coding ecosystem, like live functions, a new intuitive method for debugging live code, and adding interactive controls to your code! Super exciting.
What kind of fun things can we do with this? Well, first why don't we apply this feature to our own development approach and use it to write our tests? Do you remember way back when the test framework was still new? The second release of the test framework (R2013b) introduced the notion of function based testing, and I blogged about it on Loren's blog. We talked through at that time how we can leverage testing to ensure our engineering designs stay up to snuff in the face of a changing environment, new requirements, and code updates. I made the case that the tests become the specification, and since they are executable and can be run against all changes we know that the specification is always up to date.
This is certainly as true now as it was then, but live functions are a game changer. Why is that? Well, even though the tests act as an executable specification, most of the time we still need another form of the specification that is more readable and can be shared without getting into the nitty gritty of the code. What invariably happens then is that we end up having two specifications, the first to communicate the intent of the software, and the other to lock down that intent. Here we get to Segal's law, which states
With a test and a spec, we put ourselves in the unenviable position of having two truths. We always know that the tests keep the software and the test aligned, otherwise we fail tests and are quickly (hopefully) notified. But if someone changes the spec but forgets to update the test there is no such notification. Behavior Driven Development (BDD) evolved to try to address this by structuring the test directly in specification form and using BDD frameworks to update the spec directly from the test in a new readable format, but personally I have always found that the hoops you need to jump through with these frameworks prevent this dream from becoming a reality.
Well now that reality is here and it is called live functions! With live functions you don't need any BDD framework. The code can still look like code. It's not a big deal if test method names don't
Getting back to that blog post, it uses the standard Mass-Spring-Damper example that is just so fun to play with. When we write that test as a live function we can put all of the specification directly in the document. In fact, this blog post itself is a live function, and below we turn it into an executable specification when we make it a live function-based test. Let's see it in action.
The Spec (you know, Test)
The system we are designing is a classic mass spring damper system with the following model:
Assuming there are no external forces, the equation of motion for this system is the solution to the second order differential equatio:
What behavior of our system is needed? We define it below as our test. Our main function simply uses the test interface to begin.
function tests = testMassSpringDamper tests = functiontests(localfunctions); end
Now we can actually define all of the requirements met by our design and lock it down with the code in the same place.
Requirement 1: Settling Time
The 2% settling time of the system should be at or within 2 seconds.
Image Credit (wikipedia)
Boom! Here is the lockdown:
function testSettlingTimeRequirements(testCase) % Test that the system settles to within 0.002 of zero under 2 seconds. [position, time] = simulateSystem(springMassDamperDesign); positionAfterSettling = position(time > 2); verifyLessThan(testCase, abs(positionAfterSettling), 0.002); end
Requirement 2: Overshoot
The percent overshoot should be under 10% as governed by the equation
Image Credit (wikipedia)
function testOvershootRequirements(testCase) % Test to ensure that overshoot is less than 0.01 position = simulateSystem(springMassDamperDesign); overshoot = max(position); verifyLessThan(testCase, overshoot, 0.01); end
There we go. The code and the spec are in one place. Unlike other approaches though, neither the richness of description nor the preferred coding approach end up compromised. The code looks like standard test code. The description of the requirements and the features under test can still benefit from the richness that the live code allows. Ladies and gentlemen, I think we've found a unicorn! The live function (in other words, this blog post) is runnable just like any other test, see:
>> table(runtests('testMassSpringDamper')) Running testMassSpringDamper .. Done testMassSpringDamper __________ ans = 2×6 table Name Passed Failed Incomplete Duration Details ___________________________________________________ ______ ______ __________ _________ ____________ 'testMassSpringDamper/testSettlingTimeRequirements' true false false 0.0042491 [1×1 struct] 'testMassSpringDamper/testOvershootRequirements' true false false 0.0038952 [1×1 struct]
Do you see yourselve benefitting from combining your test and spec into a single runnable live function based test? I'd love to hear about it!
More to come (and sooner, I promise)! Readers of this blog will know that I am particularly partial to the advanced software development category. One quick gander makes it pretty obvious there is a lot more to talk about. I am pretty excited to jump into some app development workflows for example (speaking of unicorns). See you next time.