Sunday, August 20, 2017

Fast & Dirty Minimal Unit Testing

When we coders first encounter the concept of unit testing, it's usually in the context of some kind of instruction: a class or an article of some sort, maybe a video or a podcast. Most often, the instructor will demonstrate the concept of unit testing with the use of a testing framework. In Javascript, that might be QUnit or Mocha. In Java, JUnit. Etc. While a testing framework is fine, configuring it and actually using it can be a bit of a task, especially if you're doing something interesting. Sometimes you just want to code, code, code, not bean count, am I right? You're anointed. You're zooming. Ain't nobody got time for all that!

Friends, I'm here to tell you that you don't need no fancy testing framework to do unit testing! You can test right as you zoom along. Here you go. Here is your essential, no-frills framework:

const doTest= (message, result, expected) => console.assert(expected == result, message, result); // port as necessary to the language of your choice. // or change == to === as you wish.

Use it like this:

doTest("squareIt(10) should be 100 but was ", squareIt(10), 100);

Your first argument is a message ("function should return x"); second argument is literally the function calling some value; and the third is the value you expect to get.  If the expected value does not match the result, then the console will show an error:

Assertion failed: squareIt(10) should be 100 but was 101

Simple. You can open your browser console right now to see it in action.

However, it probably needs one more iteration, because this will fail when comparing non-primitive-type objects like

([1,2,3] == [1,2,3]) == false

a==b
only works if a and b are primitive types.  But remember:  this is a minimalist framework designed to get you testing without interrupting flow.  When you need an upgrade, just go ahead and modify it as you see fit.  If you find yourself needing to compare arrays, for instance, go ahead and modify the doTest. One way to solve this is to borrow yourself a fine array comparator such as:

const isEqualArray = (a, b, i = 0) => a.length != b.length ? false : i >= a.length ? true : a[i] == b[i] ? isEqualArray(a, b, i + 1) : false;

and then modify your unit test function so that  doTest checks to see if the result is an array, and then runs the isEqualArray function.

const doTest = (message, result, expected) => { const isEqual = (result instanceof Array) ? isEqualArray(result.sort(), expected.sort()) : result == expected; console.assert(isEqual, "\n" + message + "\nReceived:", result); };

Don't forget to add a unit test to make sure it's working:

doTest("Comparing the same arrays should pass: ", [1,2,3,4],[1,2,3,4]);

Boom! Your framework now compares arrays.

With doTest in place, it's easy to drop a test anywhere meaningful.  When you find yourself thinking "This function should be returning x but it's returning y.  Hmm." Just  doTest("f(x) => y", f(x), y) Then you can get to work solving the problem with a permanent, reliable partner keeping your goal on track. Later, if some fix has broken something critical, you can pin down more easily what part is broken.

doTest('complicatedFunc("What is going on??") should be "Sssh, Pumpkin. It's okay." but is '...

Also, I recommend throwing this one at the head of your list of tests:
doTest("This test should always fail.", false, true);
This will tell you that your test suite is working as expected and make your heart skip a beat at random moments. Always fun.

As a final note, obviously there are times when you will need a more sophisticated testing framework. But if you find yourself leaving constructing unit tests until the end, give this 'roll your own' approach a try. You can always easily port your tests to another framework whenever necessary.

Tuesday, August 15, 2017

BRC app update

Well, I can say that the 'map' now looks more and more like an 'app'!  Click here to see it.  It has severe UX issues, but it has a rudimentary search functionality, and some responsiveness.  The search bar is in the upper left.  Try it out!

Exciting!

Edit:  D'oh!  It fails in iPad Safari.  That's going to take a minute to troubleshoot.

Sunday, August 13, 2017

BRC map update

Further progress here.  Added art and camps with little labels.  Zoom in to see them.  Also, if you can open the javascript console in your browser (often ctrl-or-option-shift-J), you'll see the data for each marker.  It's obviously still a work in progress, but it's coming along.  While the map is for 2017, the events and camp locations are from 2016.  BMorg will release location data for the current year in about a week.

Friday, August 11, 2017

Black Rock City map update

I finished the SVG map of Black Rock City.  Feels good to get that out of the way.  Now I have a place to put location data such as art, events and camps, from the Burning Man API.  Further, with the javascript in place, it will be easier to make such maps for future years.

Edit:  (The original title of this post was Black Rock City map complete) Ah, it's an old truism in software development never to label something as 'complete', 'finished' or 'final'.  By 'finished' above, I meant only "I has a map".  The project itself is not finished.

I've since added zooming and panning capabilities. To do that, I just grabbed a library (svg-pan-zoom by ariuta)which did the essentials of what I wanted, out of the box.

However, because SVG's coordinate system increases from top to bottom, but real-life GPS increases from bottom to top (i.e. south to north, at Black Rock City, anyway), I had used SVG's transform attribute to reverse the vertical scale.  While this allowed an exact mapping between the map's xy coordinates and actual GPS coordinates, this also caused the svg-pan-zoom library's vertical panning to be reversed, as in airplane controls.

In order to get the vertical panning to work properly, I had to do without the easy fix of the reversed vertical transformation and abandon the conceptually convenient x-coordinate = longitude and y-coordinate = latitude scheme.  The new GPS => x,y  javascript transformation incorporates a vertical flip.  It took a while to get it right, but the gain is that the transformation can be any arbitrary size and origin point.

Thursday, August 10, 2017

BRC, Javascript + SVG

I'm creating a map of Black Rock City in SVG using javascript, so that I can map some of the locational Burning Man API data I've been playing with. It took me quite a lot of time to get to flow on this project. It's not quite finished, but the latest version is here.



It took some thought to translate GPS data with distances in feet, into SVG's xy-origin-at-upper-left coordinates. To make sure that everything is as it should be, for now, real GPS coordinates correspond to actual xy coordinates on the map.  e.g. "the golden spike" at 40.78660,-119.20660 in GPS is represented in the SVG graphic as <circle cx="-119.2066" cy="40.7866" r="0.0005" fill="gold"></circle>. Note how the coordinates are identical.

The problem with this is that it's necessary to zoom way, way in, because the distances involved are tiny. For example, the distance from "the golden spike" to one of the pentagon corners is 8,175 feet, but in "GPS units", it's only 0.0293. Just a single foot is 0.000003! The width and height  of the whole thing is only 0.7 x 0.6 (in SVG's unit-less distances). Such tiny numbers makes dealing with fonts difficult. But it is pretty neat, to just be able to grab a coordinate and stick it in without modification, so I'll experiment before deciding to scale it up.

Adding to my confusion is that the official GPS coordinates[pdf] for the enclosing pentagon appear to be way off (or my mind is way off). I assumed it was my transformations somehow, which is why I took extra care to make sure that there was a one-to-one correspondence between GPS coordinates and a spot on the SVG map.  There are now zero translations or transformations of any coordinates, but the official coordinates make a sad and collapsing pentagon (here shown in blue).  Way off, right?  It looks like it was crushed!

The really odd thing is that the coordinates match  Google maps almost exactly. If you enter those coordinates into Google maps, the marker will land almost precisely on the pentagon corners of last year's satellite photo of Black Rock City.  Strange.

If you have any ideas about what happened, here, let me know! I'm still baffled.

Edit:  When I calculate the distances to the Man from each of these official pentagon corners, I get these values for each, which are consistent with the visuals. They should all be equal, and they should all be more-or-less 8175. As you can see, they vary from a few hundred feet to over a thousand.

BM Official Pentagon Coordinates
GPS coordinates Distance to Man
Latitude Longitude GPS unit feet
[40.78306,-119.23568]0.02938175
[40.80652,-119.22006]0.02406709.0
[40.80247,-119.18581]0.02627298.8
[40.77657,-119.18026]0.02827865.3
[40.76448,-119.21119]0.02266304.3

The mystery deepens, but so does my confidence that it's the data and not me!

By contrast, these are the GPS values I'm using, generated by calculating 5 points 72° apart on a 8175 foot radius from the golden spike coordinate. Interestingly, one of the points almost corresponds. The order's not the same, because I generated them widdershins from high noon.

My "fuck it!" coordinates
GPS coordinates Distance to Man
Latitude Longitude GPS unit feet
[40.8073,-119.1859]0.02938175.0
[40.8127,-119.2199]0.02938175.0
[40.7820,-119.2355]0.02938175.0
[40.7577,-119.2112]0.02938175.0
[40.7733,-119.1805]0.02938175.0

Again, you can see the two pentagons here: http://rendall.tv/bm/v002/map.html

Tuesday, August 8, 2017

BM API + dataviz



I'm having fun playing with the Burning Man API and d3.  My noodling is here: http://rendall.tv/bm/v001/

I don't know if I will ever set it up to be an actual, full, proper app, but for now it's fun to see pie charts of the types of scheduled events, or bubble charts of theme camp surface area, or bar charts of the number of events each theme camp is hosting. I'm using Ramda.js to keep the data flow reasonable. As I develop it, I will likely use Rxjs to make interaction states easier to reason about.

One interesting challenge:

I'd like to get data with respect to hometowns of the various theme camps. Data for a theme camp comes in this kind of JSON object, for example:


{"uid": "a1X0V000002rka2UAA",
    "year": 2017,
    "name": "Houphoria",
    "url": null,
    "contact_email": null,
    "hometown": "Houston",
    "description": "The purpose of Houphoria is to provide multiple spaces for unique interactions to explore all of our senses. We are a collective of Houston based makers, visual artists, musicians, and DJs coming together and creating a environment for participants to come and interact with us and our creations. By day, we're a well shaded coffee lounge and bar, but at night is when our space really comes alive with vibrant lighting, sounds, and projections." }

As you can see, the hometown for the camp Houphoria is Houston. Most hometowns are pretty clear:

    San Francisco
    Los Angeles
    Unknown
    Reno
    Oakland
    Seattle
    Portland
    San Diego


However, others are not so clear:
  • Some hometown fields contain several locales, and this concept is expressed in various ways:

    • Vancouver/Louisville
    • San Frandencouver
    • Woodacre-Seattle
    • SD-OC-LA-SF
    • Portland & Bend
    • San Fran and LA
    • New York, Dubai, San Francisco, Hong Kong
    • Woodstock NY to Arcata CA, Shanghai/Bahrain/Norfolk VA
  • There are several ways to express the same city. Including misspells, San Francisco is expressed variously as:

    • SF
    • San Francisco
    • Bay Area
    • SF Bay Area
    • San Francisco bay area
    • Bay Area, CA
    • San Francisco and the surrounding Bay Area
    • San Fra
    • San Fran
    • San Fransisco
    • San Franscisco
  • Some camps add extraneous information:

    • Reno, Nevada (mostly), with a couple from Salt Lake City, Utah, and California
    • Ibiza. This is were the founding members of the camp met, most people are from different countries but Ibiza is the root
    • Nikola-Lenivets, GPS 54.75782,35.60123
If I do choose to tackle this, my approach will use a combination of normalization and regex to extract locales from the data, and then mapping to get a canonical spelling.

e.g: ["SF", "Bay Area", "SF Bay Area", "San Francisco bay area", "Bay Area, CA", "San Francisco and the surrounding Bay Area", "San Fra", "San Fran", "San Fransisco", "San Franscisco"] => "San Francisco")

At this point, though, I feel like answering this challenge would cease flow on more interesting challenges.

So why not simply leave it? In other words, why not simply treat the camp with the hometown of Los Angeles, San Francisco, New York as in a distinct bucket from San Francisco, Los Angeles, New York?

Well, that's what I'm doing, for now, but it feels inelegant. If the data will impart any insights with respect to understanding where camps are coming from, it will need to be done.

But, this project is about exploring the d3 framework in a fun way, not getting bogged down in implementation.  So, moving on.

Saturday, August 5, 2017

An American's Guide to Attending a Finnish Funeral

My friend's elderly aunt died recently, and today we went to her funeral.  There are a few differences between funerals in the US and in Finland, so if you do ever find yourself going to a funeral in Finland, here are a few tips.

Keep in mind, though, that Finns are a gracious and laid-back people, so if you find that you have been unintentionally weird and foreign, they want you to feel at ease. "Ei se mitään", they say. It's nothing.  It is more important that everyone feel comfortable than strictly adhering to protocol. 

Tip #1, is that pall bearing is no joke. In both countries, it is as close to a sacred duty as you will come, of course. But in Finland, there is no room for error, no tricks. You will carry that coffin, and you will put it in the ground. If you cannot carry heavy weight, if you even have doubt about it, do not pick it up. You seriously risk disaster. No, nothing bad happened at my friend's aunt's funeral, but there was no backup if even one of us pallbearers failed.

The second is that, in the US, there has been a shift away from solid black dress. Patterns are ok, hues are ok. As long as your clothes are not outright festive, you will be okay. In Finland, no.  Dress only in solid black without pattern. If you are a man, wear a white shirt with your black suit. It is important that your outfit not be fancy. Do not demonstrate your sartorial sophistication. You do not want to stand out.

The third difference is that Americans smile a lot.  Even at funerals, wakes, memorials and such. Lock that down. Finns smile less than Americans in general, and at funerals in particular. Do not wear a brave or comforting smile.  It will come off as weird.

The fourth tip: bring a change of more casual clothes. It is possible that you may be invited to someone's home afterwards, and they will likely immediately change out of their funeral wear. You will feel more comfortable, yourself, if you can.