Test-Driven
Development

© 2017 Morgan C. Benton code4your.life

Test-Driven
Development

Test-driven Development (TDD) is a style of writing software that relies on short iterations that start with the writing of automated tests. It has become dominant among most software development teams, and is credited for saving enormous amounts of time and money in the software development lifecycle.

© 2017 Morgan C. Benton code4your.life

Why TDD?

  1. Proves your code works
  2. Provides low-level regression test suite
  3. Allows for improving design without breaking
  4. It's more fun!
  5. Demonstrates progress at a granular level
  6. Serves as example code
  1. Forces you to plan first
  2. Reduces cost of bugs
  3. Better than code inspection
  4. Eliminates coder's block
  5. Makes for better design
  6. It's faster!

© 2017 Morgan C. Benton code4your.life

Key Question:

How will I know when I have solved the problem???

© 2017 Morgan C. Benton code4your.life

Confusing Terminology?

There's a lot of confusion/disagreement about different types of tests and different types of TDD

  • TDD vs BDD vs ATDD
  • Types of tests:
    • Unit: low-level
    • Integration
    • Acceptance

Take home message: make sure you know what your team means by all of these terms

© 2017 Morgan C. Benton code4your.life

What to Test?

  • You should only be testing code that you write
  • Do NOT test code that comes from external or 3rd party libraries (they should be writing their own tests)
  • Tests should NOT rely upon external resources, e.g. a network connection, database, filesystem, etc. 

© 2017 Morgan C. Benton code4your.life

Popular Testing Harnesses/Frameworks

© 2017 Morgan C. Benton code4your.life

Language Framework
JavaScript Jasmine, Mocha, Jest
Python py.test, nose, doctest
R RUnit, testthat
Ruby Minitest, RSpec
PHP PHPUnit, Codeception, Behat, PHPSpec
Java JUnit, JTest, Arquillian
Go testing, testify, Ginkgo, Gomega

Example:

sum(a, b)

  • Assume we have a function, sum(a, b), that accepts two parameters and returns their arithmetic sum
  • Each example shows:
    • The sum() code in its own file
    • The tests to check the correctness of sum() in a separate file
    • A screenshot of the output of running the test from the command line

© 2017 Morgan C. Benton code4your.life

Javascript/Jest

// sum.js
/**
 * Computes the sum of two numbers.
 * @param  {number} a The first number
 * @param  {number} b The second number
 * @return {number}   The computed sum
 */
const sum = (a, b) => {
  return a + b;
}

module.exports = sum;

© 2017 Morgan C. Benton code4your.life

// sum.spec.js

// import the sum.js library
const sum = require('./sum');

describe('An arithmetic library', () => {
  it('can compute sums', () => {
    expect(sum(3, 4)).toBe(7);
  })
});

Python/Pytest

def sum(a, b):
  """
  Compute the sum of two 
  numbers, a and b
  """
  return a + b

© 2017 Morgan C. Benton code4your.life

# import code to be tested
from sum import sum

# test the sum() function
def test_sum():
  assert sum(3, 4) == 7

R/Testthat

# sum.R
my_sum <- function(a, b) {
  return(a + b)
}

© 2017 Morgan C. Benton code4your.life

# tests/test_sum.R

# import library to be tested
source("../sum.R")

context("Arithmetic library")
test_that("my_sum() adds 2 numbers", {
  expect_that(my_sum(3, 4), equals(7))
})
# run_tests.R

# import testing library
library(testthat)

# run files named ./tests/test*.R
test_dir("./tests", reporter = "progress")

Ruby/MiniTest

© 2017 Morgan C. Benton code4your.life

# sum.rb
# Computes the sum of two numbers
def sum(a, b)
  return a + b
end
# sum.spec.rb
# import testing library
require 'minitest/autorun'

# import code to be tested
require_relative 'sum'

describe "An arithmetic library" do
  it "can compute sums" do
    sum(3, 4).must_equal 7
  end
end

PHP/PHPUnit

© 2017 Morgan C. Benton code4your.life

<?php // sum.php

/**
 * Calculates the sum of two numbers
 * @param  number $a The first number
 * @param  number $b The second number
 * @return number    The
 */
function sum($a, $b) {
  return $a + $b;
}
<?php // test_sum.php

use PHPUnit\Framework\TestCase;

// import code to be tested
require_once 'sum.php';

final class ArithmeticTest extends TestCase {
  public function testSum() {
    $this->assertEquals(sum(3, 4), 7);
  }
}

Java/JUnit

© 2017 Morgan C. Benton code4your.life

// Arithmetic.java
public class Arithmetic {
  public static double sum(double a, double b) {
    return a + b;
  }
}
// ArithmeticTests.java
import static org.junit.Assert.assertEquals;
import org.junit.Test;

public class ArithmeticTests {
  @Test
  public void sumTest() {
    assertEquals(Arithmetic.sum(3, 4), 7, 0.0);
  }
}

Go

© 2017 Morgan C. Benton code4your.life

package main

func sum(a float32, b float32) float32 {
  return a + b
}

func main() {}
package main

import "testing"

func TestSum(t *testing.T) {
  total := sum(3, 4)
  if total != 7 {
    t.Error("Sum fail")
  }
}

Example:

getTitle(url)

  • Assume we have a function, getTitle(url), that accepts a URL and returns the content of the <title> tag of the HTML page to which the URL points
  • Each example shows:
    • The getTitle(url) code in its own file
    • The tests to check the correctness of getTitle()
    • Sometimes a third file is necessary to make it work
    • A screenshot of the output of running the test from the command line
  • NOTE: The tests do NOT attempt to test the HTTP request since that would be testing someone else's code. Instead they all use a mock or stub to fake the HTTP call

© 2017 Morgan C. Benton code4your.life

Javascript/Jest

// get-title.js

// 3rd party library for HTTP requests
const rp = require('request-promise');

const getTitle = url => {
  return rp.request(url)
    .then(html => {
      const regex = /<title>(.*?)<\/title>/;
      let title = html.match(regex);
      return title[1];
    });
}

module.exports = getTitle;

© 2017 Morgan C. Benton code4your.life

// get-title.spec.js

const getTitle = require('./get-title');

describe('An HTML parsing library', () => {
  it('can get the <title>', () => {
    expect(getTitle('http://example.com'))
      .resolves.toBe('Cool Title, Bro');
  });
});
// __mocks__/request-promise.js
const rp = jest.genMockFromModule('request-promise');
const request = url => {
  return new Promise((resolve, reject) => {
    resolve(`
      <html>
        <head>
          <title>Cool Title, Bro</title>
        </head>
        <body>
          <p>yo</p>
        </body>
      </html>
    `);
  });
};
rp.request = request;
module.exports = rp;

Python/Pytest

# get_title.py

import requests # HTTP request library
import re       # regular expressions

def get_title(url):
  """Get the <title> from the HTML at the url"""
  r = requests.get(url)
  p = re.compile('<title>(.*?)</title>')
  s = p.search(r.text)
  return s.group(1)

© 2017 Morgan C. Benton code4your.life

# test_get_title.py

# import code to be tested
from get_title import get_title
import requests
import pytest

@pytest.fixture
def patched_requests(monkeypatch):
  def mocked_get(uri, *args, **kwargs):
    r = type('MockedReq', (), {})()
    r.text = '''
      <html>
        <head>
          <title>Cool Title, Bro</title>
        </head>
        <body>
          <p>yo</p>
        </body>
      </html>
    '''
    return r

  monkeypatch.setattr(requests, 'get', mocked_get)

# test the get_title() function
def test_get_title(patched_requests):
  title = get_title("http://example.com")
  assert title == "Cool Title, Bro"

R/Testthat

# get_title.R

library(httr)
library(stringr)

get_title <- function(url) {
  res <- GET(url)
  html <- content(res, "text")
  match <- str_match(
    pattern = '<title>(.*?)</title>', 
    string = html
  )
  match[1,2]
}

© 2017 Morgan C. Benton code4your.life

# tests/test_get_title.R

# import code to be tested
source("../get_title.R")

# load library for faking HTTP requests
library(httptest)

context("HTML Parsing Library")
test_that("get_title() can parse <title>", {
  with_mock(
    GET = function(url) {
      fake_response(url, content = '<html>
        <head>
          <title>Cool Title, Bro</title>
        </head>
        <body>
          <p>Yo</p>
        </body>
      </html>')
    },
    expect_that(
      get_title('http://example.com'),
      equals("Cool Title, Bro")
    )
  )
})
# run_tests.R

# import testing library
library(testthat)

# run files named ./tests/test*.R
test_dir("./tests", reporter = "progress")

Ruby/MiniTest

© 2017 Morgan C. Benton code4your.life

# get_title.rb

require "http"

# Gets the <title> from HTML
def get_title(url)
  html = HTTP.get(url).body
  p = /<title>(.*?)<\/title>/
  m = p.match(html)
  return m[1]
end
# get_title.spec.rb

# import testing library
require 'minitest/autorun'
require 'webmock/minitest'

# import code to be tested
require_relative 'get_title'

body = <<HTML
<html>
  <head>
    <title>Cool Title, Bro</title>
  </head>
  <body>
    <p>yo</p>
  </body>
</html>
HTML

describe "An HTML parsing library" do
  it "can get the <title> content" do
    stub_request(:any, "http://example.com").
      to_return(body: body)
    get_title("http://example.com")
      .must_equal "Cool Title, Bro"
  end
end

PHP/PHPUnit

© 2017 Morgan C. Benton code4your.life

<?php // get_title.php

namespace Morphatic;

/**
 * Gets the text from the <title> tag
 * @param  string $url The page URL
 * @return string      The <title> text
 */
function get_title($url) {
  $html = file_get_contents($url);
  preg_match('/<title>(.*?)<\/title>/', $html, $matches);
  return $matches[1];
}
<?php // test_get_title.php

namespace Morphatic;

use PHPUnit\Framework\TestCase;

// import code to be tested
require_once 'get_title.php';

// mock file_get_contents()
function file_get_contents($url) {
  return <<<HTML
<html>
  <head>
    <title>Cool Title, Bro</title>
  </head>
  <body>
    <p>yo</p>
  </body>
</html>
HTML;
}

final class HTMLParserTest extends TestCase {
  public function testGetTitle() {
    $this->assertEquals(
      get_title('http://example.com'),
      'Cool Title, Bro'
    );
  }
}

Java/JUnit

© 2017 Morgan C. Benton code4your.life

// HTMLParser.java

import java.io.IOException;
import com.google.api.client.http.apache.ApacheHttpTransport;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpResponse;
import java.util.regex.Pattern;
import java.util.regex.Matcher;

public class HTMLParser {

  private HttpTransport transport;

  public HTMLParser() {
    transport = new ApacheHttpTransport();
  }

  public HTMLParser(HttpTransport xport) {
    transport = xport;
  }

  public String getTitle(String url) throws IOException {
    GenericUrl gurl = new GenericUrl(url);
    return getTitle(gurl);
  }

  public String getTitle(GenericUrl url) throws IOException {
    HttpRequest req = transport.createRequestFactory().buildGetRequest(url);
    HttpResponse res = req.execute();
    String html = res.parseAsString();
    Pattern p = Pattern.compile("<title>(.*?)</title>");
    Matcher m = p.matcher(html);
    m.find();
    return m.group(1);
  }
}
// HTMLParserTests.java

import java.io.IOException;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import com.google.api.client.testing.http.HttpTesting;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.testing.http.MockHttpTransport;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.LowLevelHttpRequest;
import com.google.api.client.testing.http.MockLowLevelHttpRequest;
import com.google.api.client.http.LowLevelHttpResponse;
import com.google.api.client.testing.http.MockLowLevelHttpResponse;

public class HTMLParserTests {

  @Test
  public void getTitleTest() {
    HttpTransport transport = new MockHttpTransport() {
      @Override
      public LowLevelHttpRequest buildRequest(String method, String url) throws IOException {
        return new MockLowLevelHttpRequest() {
          @Override
          public LowLevelHttpResponse execute() throws IOException {
            MockLowLevelHttpResponse response = new MockLowLevelHttpResponse();
            response.setContent(
              "<html>\n" +
              "  <head>\n" +
              "    <title>Cool Title, Bro</title>\n" +
              "  </head>\n" +
              "  <body>\n" +
              "    <p>yo</p>\n" +
              "  </body>\n" +
              "</html>"
            );
            return response;
          }
        };
      }
    };
    HTMLParser p = new HTMLParser(transport);
    String title = "";
    try {
      title = p.getTitle(HttpTesting.SIMPLE_GENERIC_URL);
    } catch (IOException e) {
      System.out.println(e);
    }
    assertEquals(
      "Cool Title, Bro",
      title
    );
  }
}

Go

© 2017 Morgan C. Benton code4your.life

// htmlparser.go

package htmlparser

import (
  "net/http"
  "io/ioutil"
  "regexp"
)

func getTitle(url string) string {
  resp, err := http.Get(url)
  if err != nil { /* handle error */ }
  body, err := ioutil.ReadAll(resp.Body)
  if err != nil { /* handle error */ }
  re := regexp.MustCompile(`<title>(.*?)</title>`)
  matches := re.FindStringSubmatch(string(body));
  return matches[1]
}

func main() {}
// htmlparser_test.go

package htmlparser

import (
  "testing"
  "gopkg.in/jarcoal/httpmock.v1"
)

func TestGetTitle(t *testing.T) {
  body :=
`<html>
  <head>
    <title>Cool Title, Bro</title>
  </head>
  <body>
    <p>yo</p>
  </body>
</html>`
  httpmock.Activate()
  defer httpmock.DeactivateAndReset()
  httpmock.RegisterResponder("GET", "http://example.com",
    httpmock.NewStringResponder(200, body))
  title := getTitle("http://example.com")
  if title != "Cool Title, Bro" {
    t.Error(title + " != Cool Title, Bro")
  }
}

CoveraGe

The percentage of lines of source code in your project that are covered by tests. Target 90%. Not only do you want people to know your code is tested, but that ALL (or most) of it has been tested.

© 2017 Morgan C. Benton code4your.life

Summary

Testing is an ABSOLUTELY CRITICAL skill to have as a developer these days. You will 100% be asked to write tests if you get hired as a programmer. It's a hard habit to build if you don't start out doing it from the very beginning. Be smart. Get a head start and adopt the practice. You'll be glad you took on the extra frustration.

© 2017 Morgan C. Benton code4your.life

Test-Driven Development

By Morgan Benton

Test-Driven Development

Introduction to the test-driven development (TDD) in computer programming. Example code in a variety of languages is provided to demonstrate TDD.

  • 1,512