Programming Testing: Seams

From wikinotes

Seams are places where you can alter the behaviour of a program when it is under test.
The available seams depend on the programming language in use.
When designing a program for testing, you should provide seams to allow it to be tested.

PreProcessor Seams

In C/C++ and other languages with macros,
you can use an #ifdef to inject or replace test methods.

c example


TODO:

untested

In the real file, we include testmacros.h,
which in our tests will define functions locally.

// main.c

#include <stdio.h>
#include "libusers.h"
#include "testmacros.h"  // <-- when testing, define 'create_db_user' locally

int main(int argc, char *argv[]) {
    int id = 100;
    create_db_user("foo@example.com", "foo");  // <-- this will use our macro defined func
}

In our macro, we define a function that fakes creating a user in the database.

// testmacros.h

#ifdef TESTING

struct User {
    int  id;
    char *email;
    char *name;
}

User users[5] = {};

#define create_db_user(email, name) \
    { \
        struct User user; \
        user.id = 123; \
        user.email = email; \
        user.name = name; \
        users[0] = user; \
    }

#endif

Linker/Import Seams

You can alter the path code is sourced from to substitute in entirely new files.

CLASSPATH=test/foo:${CLASSPATH}    # java
GOPATH=test/foo:${GOPATH}          # go
PYTHONPATH=test/foo:${PYTHONPATH}  # python

Package/Module Seams

Reassign a package variable for a single testrun.

go example


This package defines OsCreate, a function that is used throghout the codebase.

// internal/fs/fs.go
package fs

import "os"

var OsCreate = os.Create // exported function, we can override

We call fs.OsCreate here

// foo.go
package main

import "foo.com/x/foo/internal/fs"

func Foo() {
    _, err := fs.OsCreate("/var/tmp/foo.txt")
    if err != nil {
        panic(err)
    }
}

In our testfile, we swap OsCreate with one that returns an error

// foo_test.go
package main

import (
    "errors"
    "foo.com/x/foo/internal/fs"
)

func TestFoo(t *testing.T) {
    var ExpectedError = errors.New("Expected")
    fs.OsCreate = func(path string) (*os.File, error) {
        return nil, ExpectedError
    }

    foo.Foo()  // <-- when foo calls 'fs.OsCreate', it will return expected error
}

Object Seams

Interface Seams