Developer Zone

Advanced Software Development with MATLAB

This is machine translation

Translated by Microsoft
Mouseover text to see original. Click the button below to return to the English version of the page.

App Exercise

Posted by Andy Campbell,

Happy Friday! Today I'd like to introduce to you Steve McClure. Steve is the lead developer for the new App Testing Framework which just came out in R2018a. We are very excited about it! It really opens up production development workflows to your MATLAB apps. Take it away Steve!

If you've designed a library of tools with lots of options or configurations, chances are that a GUI would probably make things way more convenient. It gives the user a chance to literally turn those knobs - to explore, to play, and well, not care about your underlying API. That GUI has become their API, and you owe it to them to not break it as you improve other parts of the code. This maintenance has historically come down to manually interacting with the GUI to ensure it is working properly, which instructions that might read as: choose this dropdown item, check this box, move that slider over, click the plot button, make sure it looks right. Now repeat that for every variation your system can offer and the combinatorics become overwhelming, and consumes more dev-time than you'd like. In a sense, this is already legacy code - code that's scary to touch and requires some brute-force comparisons to make sure everything still works. This largely brings about a "if it's not broke, don't fix it" policy, which is why many GUIs look like they're from the late 1990s still. With R2018a, the App Testing Framework can help you automate this process and give you and your customers a high level of confidence.

I've used App Designer to create a small App to demonstrate the classic Mass-Spring-Damper problem:

It's a pretty simple App: some numeric editfields, sliders, a continuous and a discrete knob, a gauge, and the response plot. Even so, it takes some effort to manually inspect different combinations to get a decent sense of confidence that everything is working right.

With App Designer, as you drop-in components, it adds properties to the App object, and we can easily get a display of those:

app = MassSpringDamper
app = 
  MassSpringDamper with properties:

                      UIFigure: [1×1 Figure]
                        UIAxes: [1×1 UIAxes]
               MassSliderLabel: [1×1 Label]
                    MassSlider: [1×1 Slider]
            StiffnessKnobLabel: [1×1 Label]
                 StiffnessKnob: [1×1 Knob]
            LineStyleKnobLabel: [1×1 Label]
                 LineStyleKnob: [1×1 DiscreteKnob]
            DampingSliderLabel: [1×1 Label]
                 DampingSlider: [1×1 Slider]
            InitialValuesPanel: [1×1 Panel]
    DisplacementEditFieldLabel: [1×1 Label]
         DisplacementEditField: [1×1 NumericEditField]
        VelocityEditFieldLabel: [1×1 Label]
             VelocityEditField: [1×1 NumericEditField]
                         Gauge: [1×1 SemicircularGauge]
              UnderdampedLabel: [1×1 Label]
               OverdampedLabel: [1×1 Label]
                          Line: [1×1 Line]

Notice that if we programmatically set the value App's "LineStyleKnob", ie,

app.LineStyleKnob.Value = '.-';

no callbacks fire.

This is very much by design, in that we don't want endless rounds of callbacks firing while the user is setting state behind the scenes. A tempting workaround is to manually invoke the ValueChangedFcn via an feval call:

e = struct; % fake event
feval(@app.LineStyleKnob.ValueChangedFcn, app.LineStyleKnob, e);

We've used "duck-typing" of the event, which can be hard to get right in general: you have to know what properties it has, and to keep those perfectly inline with the real system, otherwise you're not testing what you think. Another drawback of this is that it no longer respects the view. For instance, you could invoke that callback on a component that is invisible, disabled, or obscured by another component. So if you write tons of tests in this manner that all pass, does it really mean anything for your end-users? These approaches get you close, but there's still some sizeable gaps that can render an entire test bed useless.

In designing the App Testing Framework, we carefully studied and addressed these sorts of gaps. Let's create an interactive TestCase at the command line to execute gestures on the App:

testCase = matlab.uitest.TestCase.forInteractiveUse;

And now we can set the Line Style knob to "DotDashed":

testCase.choose(app.LineStyleKnob, "DotDashed")

We can see the line went to dashes! Let's use the MATLAB Test Framework to verify it programmatically:

testCase.verifyEqual(app.Line.LineStyle, '-.')
Verification passed.

This can be our first test, simply by transferring the code into a Class-Based Test that inherits from matlab.uitest.TestCase:

runtests MyAppTest
Running MyAppTest
.
Done MyAppTest
__________
ans = 
  TestResult with properties:

          Name: 'MyAppTest/testLineStyleKnob'
        Passed: 1
        Failed: 0
    Incomplete: 0
      Duration: 2.3008
       Details: [1×1 struct]

Totals:
   1 Passed, 0 Failed, 0 Incomplete.
   2.3008 seconds testing time.

There - now we have a test that we can run after every touch to the App. Fix a bug? Run the tests. Add a new feature? Run the tests. Do Test-Driven Development now to make it fail (red), make it pass (green), and don't forget to cleanup (refactor). If we flesh out our test bed well enough, an all-green test suite will give us high confidence in releasing the next version of our App.

As a note to the super-enthused, don't waste time trying to verify that the line looks dashed and the Line Style knob needle actually points to the "Dashed" label. You can trust that we've written tons of tests at the MathWorks for that sort of thing (with very similar motivations that I've laid out here). The App Testing Framework is designed to help you hit your own callback code.

Do you see where the App Testing Framework could help in your applications?

Add A Comment

Your email address will not be published. Required fields are marked *

Preview: hide