Friday, March 20, 2020

Parsing SVG documents into useful layout specifications

In a previous post, I discussed using SVG documents to specify layouts in a Unity app. More recently, I started delving into that subject and laid out a list of things to cover:

  1. How to make the SVG easy to interpret visually.
  2. How to convert the SVG into a set of rules.
  3. How to select the right rules given a query string.
  4. Generating an SVG that explains what went wrong when a test fails.

Among the many things I deferred was how to actually parse the SVG into a set of layout constraints.

Both #2 and #3 are pretty simple and they are strongly related, so I'll handle them in this text.

Thursday, March 19, 2020

Unit testing versus integration testing: I'm starting to reconsider my position

An impossible structure in an assembly diagram as might come with cheap furniture.


For a long time, I have rejected integration tests as overly costly and with very little benefit. Friends of mine have a similar argument about unit testing. They say that it does very little and slows you down.

I'm still not sold on the "unit testing slows us down" position. The negative side effects people mention (like impeding refactoring) line up more with testing implementation details than with unit testing, itself.

However, in the modern era, I'm starting to come around on integration testing.

First, I'll lay out my reasoning for why unit testing is better:
  1. Properly specifying how each individual behavior works allows you to build a foundation of working behaviors.
  2. Defining behaviors relative to other behaviors (rather than as aggregations of other behaviors) stems proliferation of redundancy in your test code.
  3. How two or more behaviors are combined in a certain case is, itself, another behavior; see #1 and #2.
I still believe all of that. If I had to choose between only unit tests or only integration tests, I would choose only unit tests.

In addition to providing better feedback on whether or not your code (the code you control) works, they also help shape your code so that defects are harder to write in the first place. By contrast, when something you wrote breaks, an integration test failing unlikely as it's very hard to create exhaustive coverage with integration tests. Furthermore, even if an integration test does fail, it's not very helpful, diagnostically-speaking.

Yet, I don't have to choose. I can have both. As we've seen in previous posts and will continue to see, I can have one scenario be bound as both.

So what is it that integration tests tell us above and beyond unit tests. My unit testing discipline makes sure that I almost never break something of mine without getting quick feedback to that effect. What do integration tests add?

The spark of realization was a recent discovery as to why a feature I wrote wasn't working but the evidence has been mounting for a while, now.  It took a lot of data to help me see the answer even though to you it may prove shockingly simple and maybe even obvious.

Over the last year or so, a theme has been emerging...

  • A 3rd party layout tool has surprising behavior and makes my layouts go all wacky. So I have to redo all my layouts to avoid triggering its bugs.
  • "Ahead of time" code fails to get generated and makes it so I can't save certain settings. I have to write code that exercises certain classes and members explicitly from a very specific perspective in order to get Unity actually compile the classes I'm using.
  • A Google plugin breaks deep linking - both a 3rd-party utility and Unity's built-in solution. I have to rewrite one of their Android activities and supplant the one they ship to make deep linking work.
  • A "backend as a service" claims that its throttling is at a certain level but it turns out that sometimes it's a little lower. I have to change how frequently I ping to something lower than what they advise in their documentation.
  • A testing library is highly coupled to a particular version of .NET and seems to break every time I update.
  • Et cetera. The list goes on...and on...and on.

Unit tests are good at catching my errors but what about all the other errors?

When you unit test and do it correctly, you isolate behaviors from one another so that each can be tested independently from another. This only works because you are doing it on both sides of a boundary and thus can guarantee that the promises made by a contract will be kept by its implementation.

That breaks down when you aren't in control of both sides of the contract. It seems like we live in unstable times. You simply can't count on 3rd party solutions to keep their promises, it seems.

This realization led me to a deeper one. It's about ownership. If you own a product, your customer only cares that it works. They don't care about why it doesn't work.

Telling them "a 3rd-party library doesn't function as promised and, as a result, deep links won't work for Android users. I'm working on it," sounds worse than just saying "Deep links don't work for Android users, I'm working on it." What they (or, at least, I) hear is "Deep links don't work for Android users. Wah, wah, wah! It's not my fault! I don't care about your inconvenience. I only care about my inconvenience. Feel sorry for me."

Even though you don't own 3rd-party code, to your customers, you may as well. You own the solution/product/game in which it is used and you own any failures it generates.

That extends well beyond whether or not the 3rd-party component/service works. It includes whether or not a component's behavior is represented correctly. It includes whether or not you used it appropriately. It even includes the behavior of a component or service changing in a surprising way.

So I finally realize the point of integration testing. It forces you to deal with the instability and fragility of the modern software development ecosystem. It's not about testing your code - that's what unit tests are for - it's about verifying your assumptions pertaining to other people's code and getting an early warning when those assumptions are violated.

Integration testing - whether it's how multiple microservices integrate or how all your components are assembled into an app - is essential. Just make sure you are using it to ask the right questions so you can actually get helpful answers:

"Is this thing I don't control properly functioning as a part of a solution I offer?"

Wednesday, March 18, 2020

The pains of being flexible

I ran a build with some pretty simple refactors as the only changes. I expected it to pass. The only reason the build even ran was that I checked in my changes.

Yet the build didn't pass. It failed with a bizarre error. SpecFlow was failing to generate code-behind files.

Some research made it clear that this was really a function of SpecFlow not working very well with .NET 3.1.200. The surprising thing about that was that I didn't remember switching to 3.1.200.

The fault actually was mine. At least, it was in a circuitous way. It was an artifact of a decision I made while defining my pipeline:

  - taskUseDotNet@2     displayName'Use .NET 3.1'     inputs:       version3.1.x       installationPath'$(Agent.ToolsDirectory)\dotnet\3.1'

I intentionally chose to allow updates automatically with that little ".x" and it burned me.

Sure enough, a tiny change "fixed" my broken pipeline:

  - taskUseDotNet@2     displayName'Use .NET 3.1'     inputs:       version3.1.102       installationPath'$(Agent.ToolsDirectory)\dotnet\3.1'

I'll definitely leave it that way until I have a reason to change it.

What I don't know, at this time, is if I'll go back to allowing the maintenance version to float, when I finally do make a change.

Is it better to have the stability of a known version or to get the fixes in the latest version without having to do anything?

Right now, my inclination is toward faster updates, still. For an indie game-development shop with a disciplined developer who jumps on problems right away, it's probably better to get the updates in exchange for the occasional quickly-fixed disruption.

That said, if I get burned like this a few more times, I might change my mind.

Tuesday, March 17, 2020

Making an SVG shape specification easy to interpret visually.

In my most recent post, I deferred describing how I parsed an SVG document to another post.

There are multiple subtopics:

  1. How to make the SVG easy to interpret visually.
  2. How to convert the SVG into a set of rules.
  3. How to select the right rules given a query string.
  4. Generating an SVG that explains what went wrong when a test fails.

I will attempt to address them all in separate posts. Like the rest of the world (at the time of this writing), I'm recovering from illness. So I'll do an easy one, now, and the rest will have to wait.

First up, how to make the SVG easy for a person to understand.

This part is all about SVG, itself. I started out with a single document that looked pretty raw - just some white rectangles on a black background. Over time I accumulated more documents and evolved them into something more palatable.

Those mostly involved the use of stylesheets and definitions but also, after much experimentation, I discovered that polyline was the most effective tool to create the shapes I wanted. I'll explain why in a bit.

First, let's look at a single polyline element:

<polyline id=".inner" points="80,192 80,1000 1860,1000 1860,192 80,192 80,193" />

That's a rectangle with its first two vertices repeated. For the test, I only need the first three points - the rest of the parallelogram is inferred.

However, to create the visual effect of a bounding box with little arrowheads pointing inward at the corners, I needed the extra points. At least, I couldn't figure out how to do it without the extra points.

I could only get the orientation of markers on inner vertices to be correct. Everything else pretty much just looked like a random direction had been chosen. As a result, I needed 4 inner vertices, which means I needed six of them, total (start, inner x 4, end).

The other structures I needed were some defined shapes to use as vertex-markers.

<defs>
    <marker id="marker-outer" viewBox="0 0 10 10" refX="5" refY="10"
        markerWidth="5" markerHeight="5"
        orient="auto">
        <path d="M 5 10 L 2 0 L 8 0 z" class="label" />
    </marker>
    <marker id="marker-inner" viewBox="0 0 10 10" refX="5" refY="0"
        markerWidth="5" markerHeight="5"
        orient="auto">
        <path d="M 5 0 L 2 10 L 8 10 z" class="label" />
    </marker>
</defs>

Once I have a rule-definition (my polyline, in this case) and the definition of the marker, I can use a stylesheet to marry the two and create the visual effect.

<style>
    *[id='inner'],
    *[id$='.inner'],
    *[id='outer'],
    *[id$='.outer']
    {
        stroke-width: 5;
        stroke: white;
        stroke-dasharray: 30 5;
        fill:none;
    }

    *[id='inner'],
    *[id$='.inner']
    {
        marker-mid: url(#marker-inner);
    }

    *[id='outer'],
    *[id$='.outer']
    {
        marker-mid: url(#marker-outer);
    }

    /* SNIPPED: Stuff used at runtime for other purposes */
</style>

Finally, to create a frame of reference, I converted a background image to base 64 and embedded it in the SVG document as the first element.

All of those steps create an effect like this:


Thankfully, most of those steps don't need to be repeated.

Sadly, it seems that SVG pushes you in the direction of redundancy. You can externalize your stylesheet but not every renderer will respect it. I couldn't find a way to reliably reuse the markers, either. The background image could be externalized but then I'd be relying on hosting for the specification to render properly.

There's a bunch of copy and paste but it's not on the critical path for the test. It just affects how the test looks to developers. So I tolerate it.

I could write a little generator that runs just before build time but then I wouldn't be able to easily preview my work.

C'est la vie.

At least, this way, I can quickly interpret my specification just by opening it in a browser or editor.