Tags: testing

RSS - Atom - Subscribe via email

First steps towards Javascript testing

Posted: - Modified: | development, geek

I know, I know, it's about time I got the hang of this. Better late than never, right? =) Anyway, I spent some time going through tutorials for QUnit and Jasmine. For QUnit, I followed this Smashing Magazine tutorial on Javascript unit testing. I modified the code a little bit to add the Z timezone to the test data, since my tests initially didn't pass.

test.html

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>Refactored date examples</title>
    <link rel="stylesheet" href="http://code.jquery.com/qunit/qunit-1.15.0.css" />
    <script src="http://code.jquery.com/qunit/qunit-1.15.0.js"></script>
    <script src="prettydate.js"></script>
    <script>
     test("prettydate.format", function() {
       function date(then, expected) {
         equal(prettyDate.format('2008/01/28 22:25:00Z', then), expected);
       }
       date("2008/01/28 22:24:30Z", "just now");
       date("2008/01/28 22:23:30Z", "1 minute ago");
       date("2008/01/28 21:23:30Z", "1 hour ago");
       date("2008/01/27 22:23:30Z", "Yesterday");
       date("2008/01/26 22:23:30Z", "2 days ago");
       date("2007/01/26 22:23:30Z", undefined);
     });
     function domtest(name, now, first, second) {
       test(name, function() {
         var links = document.getElementById('qunit-fixture').getElementsByTagName('a');
         equal(links[0].innerHTML, 'January 28th, 2008');
         equal(links[2].innerHTML, 'January 27th, 2008');
         prettyDate.update(now);
         equal(links[0].innerHTML, first);
         equal(links[2].innerHTML, second);
       });
     }

     domtest("prettyDate.update", '2008-01-28T22:25:00Z', '2 hours ago', 'Yesterday');
     domtest("prettyDate.update, one day later", '2008-01-29T22:25:00Z', 'Yesterday', '2 days ago');
    </script>
</head>
<body>
  <div id="qunit"></div>
  <div id="qunit-fixture">
    <ul>
      <li class="entry" id="post57">
        <p>blah blah blah…</p>
        <small class="extra">
          Posted <span class="time"><a href="/2008/01/blah/57/" title="2008-01-28T20:24:17Z">January 28th, 2008</a></span>
          by <span class="author"><a href="/john/">John Resig</a></span>
        </small>
      </li>
      <li class="entry" id="post57">
        <p>blah blah blah…</p>
        <small class="extra">
          Posted <span class="time"><a href="/2008/01/blah/57/" title="2008-01-27T22:24:17Z">January 27th, 2008</a></span>
          by <span class="author"><a href="/john/">John Resig</a></span>
        </small>
      </li>
    </ul>
  </div>
</body>
</html>

For practice, I converted the QUnit tests to Jasmine. The first part of the test was easy, but I wanted a clean way to do the HTML fixture-based tests for prettydate.update too. Jasmine-JQuery gives you a handy way to have HTML fixtures. Here's what my code ended up as:

spec/DateSpec.js

describe("PrettyDate.format", function() {
    function checkDate(name, then, expected) {
        it(name, function() {
            expect(prettyDate.format('2008/01/28 22:25:00Z', then)).toEqual(expected);
        });
    }
    checkDate("should display recent times", '2008/01/28 22:24:30Z', 'just now');
    checkDate("should display times within a minute", '2008/01/28 22:23:30Z', '1 minute ago');
    checkDate("should display times within an hour", '2008/01/28 21:23:30Z', '1 hour ago');
    checkDate("should display times within a day", '2008/01/27 22:23:30Z', 'Yesterday');
    checkDate("should display times within two days", '2008/01/26 22:23:30Z', '2 days ago');
});
describe("PrettyDate.update", function() {
    function domtest(name, now, first, second) {
       it(name, function() {
           loadFixtures('test_fixture.html');
           var links = document.getElementById('qunit-fixture').getElementsByTagName('a');
           expect(links[0].innerHTML).toEqual('January 28th, 2008');
           expect(links[2].innerHTML).toEqual('January 27th, 2008');
           prettyDate.update(now);
           expect(links[0].innerHTML).toEqual(first);
           expect(links[2].innerHTML).toEqual(second);
       });
    }
    domtest("prettyDate.update", '2008-01-28T22:25:00Z', '2 hours ago', 'Yesterday');
    domtest("prettyDate.update, one day later", '2008-01-29T22:25:00Z', 'Yesterday', '2 days ago');
});

jasmine.html

<!DOCTYPE HTML>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <title>Jasmine Spec Runner v2.0.2</title>

  <link rel="shortcut icon" type="image/png" href="lib/jasmine-2.0.2/jasmine_favicon.png">
  <link rel="stylesheet" type="text/css" href="lib/jasmine-2.0.2/jasmine.css">

  <script type="text/javascript" src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
  <script type="text/javascript" src="lib/jasmine-2.0.2/jasmine.js"></script>
  <script type="text/javascript" src="lib/jasmine-2.0.2/jasmine-html.js"></script>
  <script type="text/javascript" src="lib/jasmine-2.0.2/boot.js"></script>
  <script type="text/javascript" src="jasmine-jquery.js"></script>

  <!-- include source files here... -->
  <script type="text/javascript" src="prettydate.js"></script>

  <!-- include spec files here... -->
  <script type="text/javascript" src="spec/DateSpec.js"></script>

</head>

<body>
</body>
</html>

I'm looking forward to learning how to use Jasmine to test Angular applications, since behaviour-driven testing seems to be common practice there. Little steps! =)

Test-driven learning

Posted: - Modified: | learning

Mel Chua wrote a blog post about test-driven learning. When you’re learning on your own, you don’t have final exams to study for or a project that you need to submit, but it can be useful to think about objective criteria to show that you’ve learned something. Before you begin, figure out what success looks like. Define it so that it’s precise and objective. It has to be clear to you and other people when you’ve passed the test you’ve set for yourself. That way, you have a specific goal to reach for, and you know when you’ve accomplished it.

If you’re looking for ideas for tests, you might want to check out Bloom’s taxonomy, which gives you different verbs that demonstrate different levels of understanding. For example, you might start off by checking if you simply remember the concepts by being able to list them. The next level above that is demonstrating understanding, and after that, applying it to your own life. With more experience, you can analyze concepts, evaluate approaches, or create new things.

2014-02-12 Test-driven learning

2014-02-12 Test-driven learning

Inspired by Mel’s example, I’ve been thinking about how I can apply test-driven learning to the things that I’m currently focusing on.

For delegation, I can see the progress I’m making towards fully delegating my podcast production process. I’m looking forward to integrating delegation into how I create content, and how I work on tech. I think it’ll be interesting to get the point where I’m coaching other people on delegation, or when I’ve packaged guides and courses on how to do it more effectively. So, objective criteria might be:

  • I've successfully delegated “Post show notes” three times.
  • I've successfully delegated research, rewriting, and harvesting, each at least three times.
  • I've created courses on delegation for self-directed learners, bloggers, podcasters, sketchnoters, or other content creators. (Ideally, other people will have tried this!)

In terms of asking people and learning from them, I think a key step would be being able to list the current areas of focus that I have and the questions that I’m exploring. I’m getting pretty good at reaching out to people based on their Twitter updates or other incidental connections, and a step beyond that would be to make sure that these conversations are followed up with blog posts that explore the ideas in more detail. This is also related to my focus on learning how to connect with people. One thing that can help me connect with people more effectively is to organize people by topic. That way, I can focus on specific groups of people instead of letting my stream be overwhelmed by people who use social media for promotion. In particular, I’m curious about the idea of building relationships with confederates, learning more about what they are actively learning about and sharing whatever resources and ideas that I come across.

  • I've organized my Twitter into lists by subject. I regularly review each list for focused updates (one list per day), and I add people based on searches.
  • I've posted a question to my mailing list segment of people who are interested in the questions I'm learning about. (Start small!)
  • I have a dashboard showing me my conversations and ideas to follow up on, and I have turned several of those ideas into blog posts.

Packaging is straightforward to measure. I’ve published two free or pay-what-you-want resources in the past two weeks, and it will be interesting to see if I can sustain that rate until I get to 12 of them published. I’m also curious about creating courses that will allow people to track their progress, because I think will make that a lot more manageable for people to learn. Although these things are intrinsically useful and interesting, I also want to invest time in building an audience and reaching out to more people who can find these resources helpful.

  • I've created 12 free or pay-what-you-want resources, and I have a resources page that lets people find stuff they're interested in.
  • I've tried out a course or membership plugin on my site, and I've converted at least one of the resources to a course.
  • I have a plan for outreach, and I work on it regularly.

Animation is similarly easy to measure. I created a number of short videos explaining things before, so all I have to do is make that a regular practice. Again, a target of 12 short videos seems like a good number.

  • I've illustrated a course with short animations.

In terms of business, it’ll be exciting to see where my third fiscal year takes me. It looks like I’ll continue consulting for the next year, so my assessment for myself might include maintaining profitability even with my increased delegation and re-investment, and developing systems around creating and sharing content.

  • I file my next corporate tax return with all my is dotted and all my ts crossed.
  • I've posted my analysis and lessons learned about delegation and reinvestment, and my plans for next year.

I've written about this before, and every time I do, I learn something new. Other things I've learned about testing what you're learning: sharing is an excellent way to test what you know, projects help you review what you're learning, and breaking new topics down into small steps makes spiral learning more manageable.

How are you going to test what you're learning?

Back to the joys of coverage testing: Vagrant, Guard, Spork, RSpec, Simplecov

Posted: - Modified: | rails

Tests are important because programmers are human.  I know that I’m going to forget, make mistakes, and change things that I didn’t mean to change. Testing lets me improve my chances of at least noticing. Coverage tools show me how much of my code is covered by tests, improving my chances of seeing where my blind spots are.

In one of my last projects at IBM, I convinced my team to use coverage testing tools on a Rails development project. It was great watching the coverage numbers inch up, and we actually reached 100% (at least in terms of what rcov was looking at). I occasionally had to fix things when people broke the build, but sometimes people added and updated tests too. Although coverage testing has its weaknesses (are you testing for edge cases?), it’s better than nothing, and can catch some embarrassing bugs before they make it to the outside world.

Although I’m not currently taking on any web development work (I’m saving brainspace for other things), I have a few personal projects that I enjoy working on. For example, QuantifiedAwesome.com lets me track different measurements such as time. I had written some tests for it before, but since then, I’d been adding features and making changes without updating the tests. Like the way that unpaid credit card balances turn into financial debt (very bad habit, try to avoid this if possible), unwritten or out-of-date tests contribute to technical debt.

It was a little daunting to sit down and slowly work my way through the knots of out-of-date tests that had accumulated from haphazard coding. Here’s how I made the task more manageable:

  • I set up a virtual Linux development environment using Vagrant and Puppet. Many tools are much easier to set up on Linux than they are on Windows, and using a virtual machine for development means that I can easily isolate changes from the rest of my system. Vagrant allows you to specify the configuration of a VirtualBox machine and quickly set up a new instance of it. I copied the Puppet configuration from rails-dev-box and modified it to match my requirements. I also compiled and installed Emacs 24 from source, along with a few Ruby-related tools like haml-mode and rinari. (If you’re going to do a lot of development, you might as well do it with your favourite tools!) I copied the Vagrant private keys and imported them into Pageant (an ssh agent for Windows) for easy logins, and I forwarded the ports too.
  • I used Guard and Spork to efficiently run subsets of my tests. Guard is a tool that re-runs specific tests when it detects that files have changed, while Spork lets you run tests without reloading your entire Rails application. Instead of running rake spec (which runs the whole test suite) or rspec ./spec/path/to/file (and  having to copy and paste the failing test’s filename), I could let Guard take care of rerunning failed tests for me.
  • I worked on getting the old tests to pass. I wanted to get those cleared up before writing new tests. Good thing too – found a few bugs and some old code along the way.
  • I reviewed the recommendations for better tests. Better Specs has tips on using RSpec with Ruby.
  • Then I wrote new tests to cover the rest of my models. I used Simplecov for coverage testing. Since Vagrant uses shared folders, I could use Google Chrome to view the coverage webpages from my host computer. I still have some technical debt left – I need to write integration tests, and there’s more beyond that – but it’s satisfying to know that the models (data + logic) are covered. For some reason, my Guard+Spork combination doesn’t result in cumulative Simplecov scores, so I occasionally regenerate my coverage stats with rake spec. These tests will be useful as I write new features or move to Rails 4.

image

(… 21.5 hours of coding/development/infrastructure…)

New things I learned:

I use Mechanize to work with the Toronto Public Library’s webpage system so that I can retrieve the list of due books, renew items, or request holds. I wanted to test these as well. FakeWeb lets you intercept web requests and return your own responses, so I saved sample web pages to my spec/fixtures/files directory and used FakeWeb to return them. (ex: toronto_library_spec.rb)

Paperclip also needed some tweaking. I replaced my Paperclip uploads with File assignments so that I could test the image processing. (ex: clothing_spec.rb)

Always remember to wrap your RSpec tests in an it “…” instead of just context “when …” or describe “…”. I forgot to do this a few times and got confused about why stuff got left in my database and why the error messages weren’t formatted like they normally are.

Progress! Next step: Update my Cucumber tests and add some more integration tests…

I like this part of being a developer, even though writing new code is more fun. Testing is one of those time-consuming but powerful tools that can reduce frustration in the long run. I’ve experimented with applying automated testing techniques to everyday life before. I wonder what it would be like to take that even further… Any thoughts?

Running groups of Drupal tests from the command line

| drupal

I’ve written about using drush to evaluate PHP statements in the Drupal context using the command line before, and it turns out that Drush is also quite useful for running Simpletest scripts. Drush comes with a module that allows you to display all the available tests with “drush test list”, run all the tests with “drush test run”, or run specified tests with “drush test run test1,test2”.

‘Course, I wanted to run groups of tests and tests matching regular expressions, so I defined two new commands:

drush test run re regular-expression
Run all tests matching a regular expression that uses ereg(..) to match.
Ex: drush test run re Example.*
drush test run group group1,group2…
Run all tests matching the given groups
Ex: drush test run group Example

Here’s the patch to make it happen:

Index: drush_simpletest.module
===================================================================
--- drush_simpletest.module	(revision 884)
+++ drush_simpletest.module	(working copy)
@@ -12,9 +12,13 @@
 function drush_simpletest_help($section) {
   switch ($section) {
       case 'drush:test run':
-        return t("Usage drush [options] test run.\n\nRun the specified specified unit tests. If  is omitted, all tests are run.  should be a list of classes separated by a comma. For example: PageCreationTest,PageViewTest.");
+        return t("Usage drush [options] test run .\n\nRun the specified unit tests. If  is omitted, all tests are run.  should be a list of classes separated by a comma. For example: PageCreationTest,PageViewTest.");
       case 'drush:test list':
         return t("Usage drush [options] test list.\n\nList the available tests. Use drush test run command to run them. ");
+      case 'drush:test group':
+        return t("Usage drush [options] test group .\n\nRun all unit tests in the specified groups. For example: drush test group Group1,Group2");
+      case 'drush:test re':
+        return t("Usage drush [options] test re .\n\nRun all unit tests matching this regular expression. For example: drush test re Page.*");
   }
 }
 
@@ -30,10 +34,18 @@
     'callback' => 'drush_test_list',
     'description' => 'List the available Simpletest test classes.',
   );
+  $items['test re'] = array(
+    'callback' => 'drush_test_re',
+    'description' => 'Run one or more Simpletest tests based on regular expressions.',
+  );
+  $items['test group'] = array(
+    'callback' => 'drush_test_group',
+    'description' => 'Run one or more Simpletest test groups.',
+  );
   return $items;
 }
 
-function drush_test_list() {
+function drush_test_get_list() {
   simpletest_load();
   // TODO: Refactor simpletest.module so we don't copy code from DrupalUnitTests
   $files = array();
@@ -60,6 +72,11 @@
       $rows[] = array($class, $info['name'], truncate_utf8($info['desc'], 30, TRUE, TRUE));
     }
   }
+  return $rows;
+}
+
+function drush_test_list() {
+  $rows = drush_test_get_list();
   return drush_print_table($rows, 0, TRUE);
 }
 
@@ -75,3 +92,31 @@
   }
   return $result;
 }
+
+function drush_test_re($expression) {
+  if (!$expression) {
+    die('You must specify a regular expression.');
+  }
+  $rows = drush_test_get_list();
+  $tests = array();
+  foreach ($rows as $row) {
+    if (ereg($expression, $row[0])) {
+      $tests[] = $row[0];
+    }
+  }
+  simpletest_run_tests($tests, 'text');
+  return $result;
+}
+
+function drush_test_group($groups) {
+  $rows = drush_test_get_list();
+  $tests = array();
+  $groups = explode(',', $groups);
+  foreach ($rows as $row) {
+    if (in_array($row[1], $groups)) {
+      $tests[] = $row[0];
+    }
+  }
+  simpletest_run_tests($tests, 'text');
+  return $result;
+}

That makes running tests so much easier and more fun!