QUnit – Tech Learning

This post is part of my tech learning series, where I take a few hours each week to try out a piece of technology that I’d like to learn.

This time I’m back to using JavaScript but instead of using a framework or library to build an application, I decided to try out the testing framework called QUnit.

QUnit came from the jQuery project and is now a general purpose JavaScript testing framework. I’ve used Jasmine for my JavaScript testing in the past but I’m not a fan of the BDD/spec style it uses. Maybe that’s because I learned unit testing on Ruby’s Test::Unit but I’ve always preferred assert style tests over expect.

Testing with QUnit

Since I don’t want to build an application just to use QUnit, I decided to port an existing test suite from Jasmine to QUnit. My A/B Test Calculator was the perfect candidate:

  • it’s small
  • it’s self-contained
  • it has an existing, high coverage test suite

Getting started with QUnit was easy. Their documentation was pretty through and all that I needed was to create an HTML file that did a few things:

  • loaded the QUnit css
  • had a div with the id of qunit
  • had a div with the id of qunit-fixture (though I didn’t use it in these tests)
  • loaded the QUnit JavaScript
  • loaded my test suite

Autostart

One thing that threw me for a loop was I was trying to use grunt to run the tests on the commandline. Normally QUnit runs automatically on the page load, but in Grunt that wouldn’t work.

Instead, you need to stop QUnit from starting automatically, load your tests, and then start QUnit manually.

  <script src="qunit-1.14.0.js"></script>
  <script>QUnit.config.autostart = false;</script>
  <script src="tests.js"></script>
  <script>QUnit.start();</script>

Test layout

Instead of using nested functions or methods like Jasmine and Ruby to separate contexts, QUnit takes a simpler approach. Making a function call to QUnit.module("name") will end your current context and start a new one.

At first I thought this was confusing but it actually prevented my tests from getting deeply nested. I could see this being an advantage when refactoring tests because you wouldn’t need a dummy commit to re-indent everything after moving code around.

Each module can also have setup and teardown functions that run but none of my tests needed them.

Tests

Each test is started using QUnit.test("test name"), function(assert) with the function acting as your test body. The assert object is where your assertions are called on. Though I wouldn’t do it, you could use a different name like expect and make it spec-like:

QUnit.test("test name"), function(expect) {
  expect.equal(a, b)
}

Calling it should might be interesting though:

QUnit.test("test name"), function(should) {
  should.equal(a, b)
}

Running QUnit tests

Running the tests couldn’t have been easier. I was using grunt on the commandline and they’d work exactly how you think.

I was also able to open the test HTML file in a browser using file:/// and QUnit worked exactly how you’d expect.

Summary

I really enjoyed using QUnit. It feels more direct and simple than Jasmine though I don’t know how many third party libraries are out there for it.

I’d also be curious to see how to integrate it with Rails and the asset pipeline. That’s one of the main reasons why I started with Jasmine, it had some great Ruby integration and it felt like a good fit there.

I’ll definitely be adding QUnit to my standard toolkit. It won’t be replacing Jasmine yet, at least until I get an opportunity to try QUnit in Rails and in a production application.

Would you like to hire me?

Code

The HTML test runner which loads the tests and qunit.

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>QUnit Tech Learning</title>
  <link rel="stylesheet" href="qunit-1.14.0.css">
</head>
<body>
  <div id="qunit"></div>
  <div id="qunit-fixture"></div>
 
  <script src="../src/qunit-tech-learning.js"></script>
 
  <script src="qunit-1.14.0.js"></script>
  <script>QUnit.config.autostart = false;</script>
  <script src="tests.js"></script>
  <script>QUnit.start();</script>
</body>
</html>

The test suite which has two modules.

QUnit.module("Sanity");
QUnit.test("sanity test", function(assert) {
  assert.ok(1 == "1");
});
QUnit.test("SplitReview loaded", function(assert) {
  assert.ok(SplitReview);
});
 
 
// Based on abingo and http://20bits.com/article/statistical-analysis-and-ab-testing
var treatmentA = [
  { impressions: 182, conversions: 35},
  { impressions: 180, conversions: 45}
];
var treatmentB = [
  { impressions: 182, conversions: 35},
  { impressions: 189, conversions: 28}
];
var treatmentC = [
  { impressions: 182, conversions: 35},
  { impressions: 188, conversions: 61}
];
var treatmentD = [
  { impressions: 182, conversions: 35},
  { impressions: 189, conversions: 14}
];
var treatmentE = [
  { impressions: 182, conversions: 282},
  { impressions: 189, conversions: 114}
];
 
QUnit.module("SplitReview#score");
QUnit.test("should return a scoring object", function(assert) {
  var result = SplitReview.score(treatmentA);
 
  assert.equal(typeof(result), "object");
  assert.ok(result.z, "z should be defined");
  assert.ok(result.p, "p should be defined");
  assert.ok(result.winner, "winner should be defined");
 
  assert.ok(result.a_rate)
  assert.ok(result.b_rate)
});
 
QUnit.test("should show the experiment as a winner against treatment A at 90%", function(assert) {
  var result = SplitReview.score(treatmentA);
 
  assert.equal(result.a_rate, 19.23);
  assert.equal(result.b_rate, 25.0);
  assert.equal(result.winner, 'b');
  assert.equal(result.z, 1.33);
  assert.equal(result.p, 0.10);
});
 
QUnit.test("should show no winner against treatment B", function(assert) {
  var result = SplitReview.score(treatmentB);
 
  assert.equal(result.a_rate, 19.23);
  assert.equal(result.b_rate, 14.81);
  assert.equal(result.winner, null);
  assert.equal(result.z, -1.13);
  assert.equal(result.p, null);
});
 
QUnit.test("should show the experiment as a winner against treatment C at 99%", function(assert) {
  var result = SplitReview.score(treatmentC);
 
  assert.equal(result.a_rate, 19.23);
  assert.equal(result.b_rate, 32.45);
  assert.equal(result.winner, "b");
  assert.equal(result.z, 2.94);
  assert.equal(result.p, 0.01);
});
 
QUnit.test("should show the control as a winner against treatment D at 99.9%", function(assert) {
  var result = SplitReview.score(treatmentD);
 
  assert.equal(result.a_rate, 19.23);
  assert.equal(result.b_rate, 7.41);
  assert.equal(result.winner, "a");
  assert.equal(result.z, -3.39);
  assert.equal(result.p, 0.001);
 
});
 
QUnit.test("should show the control as a winner against treatment E at 99.9% (over 100% conversion rate based on unique counting)", function(assert) {
  var result = SplitReview.score(treatmentE);
 
  assert.equal(result.a_rate, 154.95);
  assert.equal(result.b_rate, 60.32);
  assert.equal(result.winner, "a");
  assert.equal(result.z, -12.27);
  assert.equal(result.p, 0.001);
 
});
Go logo, by Renée French

Go – Tech Learning

This post is part of my tech learning series, where I take a few hours each week to try out a piece of technology that I’d like to learn.

This week I decided to try something completely new and spend some time with Go.

Go is a programming language that has seen some success in system programming. My goal with learning Go is to replace some shell scripts and services I run.

This tech learning was a bit harder though, since Go is an entire programming language and a type of language I don’t have much experience with.

Documentation

The official Go website has a lot of official documentation. The Tour of Go and How to write Go code sections were useful for me to get started. Other sites like Stackoverflow where useful for specific problems.

Todo list application with Go

Like other weeks, I wanted to build a todo list application with Go. I’ve done enough of them that I know the scope of the application, so I can focus on learning how Go works.

This time the application was going to be purely command line based. I know Go can be used for web applications but I wanted to keep things simple in my limited time.

Features

That meant that the feature-set would stay small. But even then, I didn’t have enough time to complete them all.

  1. List all open todo items
  2. Add a new todo item
  3. Complete a todo item
  4. Delete a todo item
  5. Save todo items to disk for persistence

Implementation

Being a command line application, my todo program would be using standard input and output for most of its work. I also figured I’d persist the todo items into a file, but I was open to the format of the file (e.g. serialized, json, text).

main(), command parsing

The first part was to accept commands from the command line. I’ve always like the svn and git style of subcommands over flags so I designed this application the same way. It would be called like:

todo COMMAND OPTIONS
todo list
todo add "The todo item text"
todo complete N
todo remove N

For this I found the os package with the os.Args function to work great. I had to do a bit of input handling to check the options but a multi-step if statement worked for that (which I plan to change to a switch later).

Once the correct command was found, the flow would be passed off to the instance of the todolist.

todolist and todo structures

The main object used is a todolist structure which holds an array of todos. These todos are composed of a name and a done flag.

My intention was to have a single todolist that would serve as the behavior for managing the list.

todolist.list()

The list() function would take the existing todolist and print out every todo item to standard output. Nothing too complex here, just a for loop and fmt.Println.

todolist.add()

The add() function would take a string, from the command line, and create a new todo item on the list with it.

The interesting thing here was how Go treats arrays and slices. Coming from a Ruby background, it felt odd. What would have been a simple append operation, took a bit of research to figure out. Even though I ended up using the append function, I couldn’t tell you if it was creating a new array, creating a slice, or creating a slice and converting it into an array.

The final code works and is simple to read, but I’d like to dig into what’s going on to better understand it later.

todolist.save() and todolist.load()

Before I was done I wanted to work on the persistence. What good is a todo list that isn’t saved?

(Actually, that might be a good idea if you’re overworked…)

I intending to have a function called save() that would take the current todo items and save them to a file, along with a corresponding load(). Ideally they’d be in a plain text or human readable format but I was open to any format really.

The problem I ran into was saving the todo items to disk. I needed a byte array to save to the file, but each todo item was using a string. Since there could be zero to a near-infinite number of todo items, I had to be dynamic in how I got each item. I tried a few different conversion methods but I couldn’t get them to completely work.

I’d like to blame my lack of a C background for this.

If I had a bit more time, I’m sure I could have figured it out.

Summary

Go was a different enough language from regular work so it stayed interesting. I was frustrated by the type system but I think that’s because I didn’t spend enough time with it.

The ability to compile to a static binary really appeals to me. That feature alone would make it worth porting some Ruby scripts I have to Go (e.g. text transformation, basic network I/O).

Overall, I’m still interested in Go. I’m not ready to port anything over to it or to take on a project that uses Go heavily, but I’m interested enough to learn more about it.

Need custom Ruby development?

Code

package main
 
import (
	"fmt"
	"os"
)
 
type todolist struct {
	todos []todo
}
 
type todo struct {
	name string
	done bool
}
 
 
// 2. Add new
func (l *todolist) add(name string) bool {
	newItem := todo{name: name, done: false}
	l.todos = append(l.todos, newItem)
	return true
}
 
// 1. List open todos
func (l *todolist) list() bool {
	fmt.Println("Your todo list is:")
	for i := 0; i < len(l.todos); i++ {
		fmt.Println("- ", l.todos[i].name)
	}
	return true
}
 
// 3. Complete
// 4. Remove
 
// command data
func main() {
	var command, data string;
 
	if len(os.Args) > 1 {
		command = os.Args[1]
	}
	if len(os.Args) > 2 {
		data = os.Args[2]
	}
 
	l := new(todolist)
 
	if command == "list" {
		// No-op, always print list
	} else if command == "add" {
		l.add(data)
	} else if command == "complete" {
 
	} else if command == "remove" {
 
	} else {
		fmt.Println("Unknown command:", command)
		os.Exit(1)
	}
 
	// Always print
	l.list()
 
}
AngularJS-large

AngularJS – Weekly Tech Learning

This post is part of my weekly tech learning series, where I take a few hours each week to try out a piece of technology that I’d like to learn.

After last week’s experience with riot.js I decided to experiment with another JavaScript library/framework I’ve been hearing a lot about, AngularJS.

Angular bills itself as a way to extend HTML for dynamic views in web applications. This sounded a bit strange but after seeing how Angular works and embeds itself into HTML, it makes sense.

Documentation

The first place I start with anything new is the documentation. I’m the kind of person who reads the manual for a new camera before using it.

Angular’s documentation really shines. The homepage has videos, several example apps, and there are a ton of resources deeper into the site.

What was really surprising is that Angular exceptions have a link on the JavaScript console that point to the angular website with details about what happened. This made debugging much easier because I could take my specific exception and find out what it meant in the more general sense.

Todo list application with AngularJS

Like my other applications, I built a todo list application with Angular. I’ve done enough of them that I know the scope so I can focus learning how Angular works.

Surprising, one of the demo applications on Angular’s homepage was a simple todo application. If you compare it to mine, you’ll notice I used much of the same code. But I still built my by hand so I can see how it works step-by-step.

Features

Being a todo list, I kept the feature-set short.

  1. Single page app without refreshes
  2. List all open todo items
  3. Add a new todo item
  4. Complete a todo item
  5. Delete a todo item

Since the demo application did most of this, I had extra time to add a basic localStorage persistence feature.

HTML

The first thing I noticed with Angular is that much of its view is put into the HTML. You decorate various elements and sections with ng values to connect them to Angular. This was a bit shocking to me, since years ago there were movements to get JavaScript out of the HTML (unobtrusive JavaScript, progressive enhancement). To see non-standard HTML used by Angular felt odd, like a step backwards.

But if you go back to what Angular’s goals, it makes sense. It wants to extend HTML, which means it will be changing what I’d consider HTML.

The first hiccup I had was getting my controller to connect. I had my ng-app and ng-controller declared, Angular was loading, and everything looked right. But my controller kept appearing as undefined. After some debugging, I found I needed to use ng-app differently than the demo code. I needed to fully declare my app (ng-app vs ng-app='tech-learning').

Once that was in place, things started working like they were supposed to.

Data binding

Like knockout.js, AngularJS puts some control flow and loops directly into the HTML. In my case I needed that for listing each todo item with a standard template (ng-repeat). In that loop, {{todo}} was used to reference the current todo item, just like most templates.

Completing a todo

The interesting thing with the demo application is that they used only the data binding to mark a todo as complete or not. The checkbox has a ng-model="todo.done" which toggles the done value of a todo object. This happens automatically and it also changes the css class for the todo text. With some css, that means it automatically gets a strike-through when it’s completed, right from the checkbox.

This did cause a problem with my localStorage though. Because the view was accessing the model directly, I couldn’t call the function to persist the data. I’m sure if I had more time I could hook up the persistence a bit more transparently so ng-model would automatically save.

Controller (and Model, kinda)

With the HTML page acting as the View, a JavaScript file functions as the Controller. What was odd, coming from a Rails background, is that the Controller also embedded the Model. The Model wasn’t explicitly declared but you could see it.

Maybe this was because a todo list application is simple so it doesn’t need a separate Model. But it still seemed like keeping them together would be a bit of a pain to test and maintain over time.

Much of Angular’s View-Controller connection is done through the scope, or $scope in the code. This seems to be a shared object that the view can access as well as the controller where it’s passed in as a parameter.

Data and behavior defined on this scope object let the View call it. In fact, I guess this ease of callability is what helps Angular be easy to develop with.

load()

The load() function was an addition that I added to support localStorage. Basically it checks localStorage to see if there are any todo items there and returns them.

This works in conjunction with the todos variable which is where the controller tracks the list of todo items (and which the View uses to build its list).

add()

This function has three behaviors:

  1. Add a new todo item based on the form submission
  2. Clear the todo field name
  3. Persist the data to localStorage

The View is hooked up to this through the ng-submit="add()" declaration on the form. This works like a binding that takes the submit events and sends it to the add() function. The name field is also given a ng-model declaration so the controller can access it (e.g. getting the value, clearing).

deleteTodo()

I also wanted to be able to delete a todo item. The deleteTodo() function does this by searching for the todo item and removing it from the todos array.

The interesting thing was that I hooked it up using the ng-click and was able to pass in the current todo from the View. This meant I didn’t have to bother with ids or some unique key to search on.

persist()

The persist() function is just a simple little wrapper for saving the current todos into localStorage.

localStorage compatibility

Based on my experience with localStorage in the past, I’ve created a simple compatibility object for browsers who don’t support localStorage. All it does it mockup the API I need with no-op functions.

(In a larger application, this isn’t the best approach. Feature detection will notice this object and think that a browser actually does support localStorage. So watch out if you try this.)

Final thoughts

That’s it. All together, my AngularJS todo application is around 30 lines of JavaScript with a dozen or so of HTML. It’s one of the smaller ones I’ve built, which isn’t surprising since Angular is supposed to be a higher-level library than others.

Overall though, I wasn’t happy with a few things in AngularJS.

Code in HTML

I strongly dislike putting that much “code” into my HTML. I’ve seen too many teams start with a little bit of code and then they end up with a mess that is impossible to work in. (knockout.js also suffers from this.

With a good team, the right practices, and an eye for problems this could be worked around. But it’s easy to fall off.

(Granted the alternatives aren’t much better: building HTML strings in JavaScript is horrible and JavaScript templates can become a pain too. It’s not an easy problem.)

One plus with AngularJS from what I understand is that you can scope it to a specific part of the HTML document. So instead of one ball of mess, you might have several smaller Angular controllers.

(I also seems like SEO can be a bit of an issue with angular. Some people say angular content is invisible because it’s JavaScript, others say that Google will run angular code and see the content. All I know is that you’ll have to do your own tests if you have a public angular site.)

Large API

AngularJS’s API is large. Even if you’re just looking at the core, there’s a lot there. It seems like it also has its own API for events, which could be interesting if you use jQuery also.

As part of my advancing age, I’m starting to prefer libraries and frameworks with a smaller footprint. Not just in size but also in how many methods/functions they expose. Having less of an API to remember means more of my application can fit in my head.

It was good to see Angular has separated some components out of the core, which reduces both its size and API footprint.

Summary

angularjs-quote

I can see using AngularJS with some applications. I’m not putting it into my general purpose toolkit quite yet, but I’d feel comfortable working with it in an existing codebase.

I also hear the 2.0 version is going to have some significant changes which improve some of the problem areas.

If you’d like to use a demo version of the application, you can find a copy running at http://angular-tech-learning.apps.littlestreamsoftware.com/ with the unminified version of the JavaScript and localStorage.

Need custom JavaScript development?

Code

Basic HTML page with the skeleton structure, ng declarations, and the todo list.

<!-- www/index.html -->
<!DOCTYPE html>
<!--[if lt IE 7]>      <html ng-app='tech-learning'  class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]>         <html ng-app='tech-learning'  class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]>         <html ng-app='tech-learning'  class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html ng-app='tech-learning' class="no-js"> <!--<![endif]-->
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
        <title></title>
        <meta name="description" content="">
        <meta name="viewport" content="width=device-width">
 
        <!-- Place favicon.ico and apple-touch-icon.png in the root directory -->
 
        <link rel="stylesheet" href="css/normalize.css">
        <link rel="stylesheet" href="css/main.css">
        <link rel="stylesheet" href="css/app.css">
    </head>
    <body ng-controller="TodoController">
 
      <h1>Todos</h1>
 
      <ol id="todos">
        <!-- http://mutablethought.com/2013/04/25/angular-js-ng-repeat-no-longer-allowing-duplicates/ -->
        <li ng-repeat="todo in todos track by $id($index)">
          <input type="checkbox" ng-model="todo.done">
          <span class="done-{{todo.done}}">{{todo.text}}</span> 
          <a class="todo-delete" ng-click="deleteTodo(todo)" href="#">(X)</a>
        </li>
      </ol>
 
      <form id="addNewTodo" ng-submit="add()">
        <p>
          <input id="todoName" name="name" ng-model="todoText" />
          <button type="submit">Add</button>
        </p>
      </form>
 
        <!-- JS -->
        <script type='text/javascript' src='js/jquery-2.1.0.min.js'></script>
        <script type='text/javascript' src='js/angular.min.js'></script>
        <script type='text/javascript' src='js/tech-learning-angular.js'></script>
    </body>
</html>

The full angularJS application, including the simple localStorage wrapper.

// www/js/tech-learning-angular.js
// Test if localStorage is present and supported in the browser
if (typeof(localStorage) == 'undefined') {
  localStorage = new Object();
  localStorage.getItem = function(key) { }
  localStorage.setItem = function(key, value) { }
  localStorage.clear = function() { }
}
 
 
var todoApp = angular.module('tech-learning', [])
  .controller('TodoController', ['$scope', function($scope) {
    $scope.load = function() {
      var todosJSON = localStorage.getItem("todos-angular");
      if (todosJSON) {
        return JSON.parse(todosJSON);
      } else {
        return [];
      }
    }
 
    $scope.todos = $scope.load();
 
    $scope.add = function() {
      $scope.todos.push({text: $scope.todoText, done: false});
      $scope.todoText = '';
      $scope.persist();
    };
 
    $scope.deleteTodo = function(todo) {
      var index = $.inArray(todo, $scope.todos);
      if (index > -1) {
        $scope.todos.splice(index, 1);
      }
      $scope.persist();
    };
 
    $scope.persist = function() {
      localStorage.setItem("todos-angular", JSON.stringify($scope.todos));
    };
  }]);
ideal-clients

Ideal Clients

A bit of truth for you: Working with great clients is 100x better than bad clients.

But how do you actually find these great clients?

By focusing on your ideal clients.

An ideal client is a person who is a perfect fit for you and your services. It’s a standard you can measure potential clients against.

But there are two things you must do before you can work with your ideal clients.

First, you need to describe who they are, and what they do. Only once you’ve clearly defined them, then you can start looking for great clients.

(And actually be confident that you’ve found one)

Second, you need to regularly review your ideal client definition. You can make plans and guess all you want, but until you get feedback on your definition you won’t know for sure if you’re right. Regular review helps you process that feedback and adapt to it.

Do this and create a process around it, and your client quality will improve. You’ll work on better projects, with better people, and be happier with your work.

Eric Davis

Want weekly freelancer training?

Redmine Consulting Services Open

My company, Little Stream Software has re-opened its Redmine and ChiliProject consulting services.

If your Redmine or ChiliProject installation needs help, I’m here for you.

  • Maybe your team needs to change the workflows.
  • Maybe you need some expert training on customizations and plugin development.
  • Or maybe you want to extend it to better fit your business processes.

Whatever the reason, if you’re interested you can learn more about how I can help you:

Learn more about my Redmine services

Helping new entrepreneurs build a successful software business.

Need more freelance clients?