Saturday, 6 July 2013

Coding the Simplest Testing Framework (Java)

There are loads of testing frameworks out there. Some very complex, powerful and excellent. I wouldn't be without them, but they're potentially quite difficult for those unused to the concept. While working on an embedded project, with very few tools at my disposal, I quickly whacked together this simple testing framework and decided to blog it.

Essentially you would put this import at the top of your code:

import static net.intrepidis.library.SimplestTest.test;

Then you can use it like this:
        test(7+8).is(15);

What will happen is that if the test fails then an exception is thrown. Essentially that is all there is to it. Proper testing frameworks catch this and display very helpful messages at the end of all the tests. Please visit this other blog for some real-world examples:
Using TotallyLazy the functional library for Java

For a "proper" testing framework I recommend Hamcrest. Meanwhile, below is the actual SimplestTest code. As you can see, it's very simple... The bottom half is specifically for accepting TotallyLazy sequences.

package net.intrepidis.library;

import com.googlecode.totallylazy.Arrays;
import com.googlecode.totallylazy.Sequence;
import com.googlecode.totallylazy.Sequences;
import java.util.List;
import static com.googlecode.totallylazy.Sequences.sequence;

public class SimplestTest {
    public static <T> TestObject<T> test(T o) {
        return new TestObject<T>(o);
    }
    public static class TestObject<T> {
        private final T o;
        public TestObject(T o) {
            this.o = o;
        }
        public void is(T r) throws Exception {
            if (!toCheck(o).equals(r)) {
                throw new Exception("is but \"" + o + "\" != \"" + r + "\"");
            }
        }
        public void isnt(T r) throws Exception {
            if (toCheck(o).equals(r)) {
                throw new Exception("isnt but \"" + o + "\" == \"" + r + "\"");
            }
        }
        private static Object toCheck(Object o) {
            if (o instanceof CharSequence) {
                return o.toString();
            }
            return o;
        }
    }
    
    public static <T> TestSequence<T> test(Sequence<T> s) {
        return new TestSequence<T>(s);
    }
    public static class TestSequence<T> {
        private final Sequence<T> s;
        public TestSequence(Sequence<T> s) {
            this.s = s;
        }
        public void is(T...a) throws Exception {
            List<T> list = Arrays.list(a);
            if (!Sequences.equalTo(s, list)) {
                throw new Exception("is but " + s.toString() + " != " + sequence(a).toString());
            }
        }
        public void isnt(T...a) throws Exception {
            List<T> list = Arrays.list(a);
            if (Sequences.equalTo(s, list)) {
                throw new Exception("isnt but " + s.toString() + " == " + sequence(a).toString());
            }
        }
    }
}

This code is completely free to use by anybody. It is held under the Do What You Want To Public License: http://tinyurl.com/DWYWTPL

Pattern Matching (Java - non-generic)

The purpose of this page is to document a pattern matching class (without generics) as an accompaniment to the blog Using TotallyLazy the functional library for Java. To view a more complex version of the class (which does use generics) please go to Pattern Matching (Java - using generics).

I use pattern matching a lot in functional languages and find it very natural and readable (to the initiated). Therefore, when I come back to imperative languages such as Java, I find wading through the many nested branching 'if' statements quite horrible and antiquated. For this reason I decided to create a pattern matching class in Java.

Here is an example of how to use the pattern matching class. At the end the 'matchResult' string will equal "47f" because the input parameters match the "with(_, 'f')" method. The underscore character '_' is used as a wildcard.

import com.googlecode.totallylazy.Function1;
import static net.intrepidis.library.functional.PatternMatch._;
import static net.intrepidis.library.functional.PatternMatchMany.matchMany;
class Example {
    void example() {
        String matchResult =
            matchMany(7, 'f').returns(String.class)
            .with(1, 'a').then(returnWith("1"))
            .with(2, 'f').then(returnWith("2"))
            .with(1, 'f').then(returnWith("3"))
            .with(_, 'f').then(returnWith("4"))
            .with(1, _).then(returnWith("5"))
            .with(_, _).then(returnWith("6"))
            .result();
    }

    private static Function1<Object[], String> returnWith(final String position) {
        return new Function1<Object[], String>() {
            public String call(Object[] ps) {
                return position + ps[0] + ps[1];
            }
        };
    }
}

Below is the full listing of the PatternMatchMany class:


package net.intrepidis.library.functional;

import com.googlecode.totallylazy.Function1;

import static net.intrepidis.library.functional.PatternMatch.areEqual;
import static com.googlecode.totallylazy.Sequences.sequence;

public class PatternMatchMany<TR> {
    private boolean matched = false;
    private TR result;
    private final Object[] parameters;

    private PatternMatchMany(Object[] parameters) {
        this.parameters = parameters;
    }

    public static PatternMatchMany<Object> matchMany(Object... parameters) {
        return new PatternMatchMany<Object>(parameters);
    }

    @SuppressWarnings("UnusedParameters")
    public <UR> PatternMatchMany<UR> returns(Class<UR> returnType) {
        return new PatternMatchMany<UR>(parameters);
    }

    public TR result() {
        return result;
    }

    public With with(Object... candidates) {
        return new With(candidates);
    }

    public class With {
        private boolean act = false;

        private With(Object[] candidates) {
            if (matched) {
                // A match has already been found, so don't match again.
                return;
            }

            // Are any unmatched?
            boolean areAnyDifferent =
                    sequence(candidates) // These possible matches...
                            .zip(sequence(parameters)) // pair up with these inputs...
                            .map(areEqual) // make 'true' if they're equal...
                            .contains(false); // return true on the first non-match.

            if (!areAnyDifferent) {
                // All parameters have matched.
                act = matched = true;
            }
        }

        public PatternMatchMany<TR> then(TR returnValue) {
            if (act) {
                result = returnValue;
            }
            return PatternMatchMany.this;
        }

        public PatternMatchMany<TR> then(Function1<Object[], TR> actor) throws Exception {
            if (act) {
                result = actor.call(parameters);
            }
            return PatternMatchMany.this;
        }
    }
}

This code is completely free to use by anybody. It is held under the Do What You Want To Public License: http://tinyurl.com/DWYWTPL

Pattern Matching (Java - using generics)

The purpose of this page is to document a pattern matching class (which uses generics) as an accompaniment to the blog Using TotallyLazy the functional library for Java. To view a simpler version of the class (without generics) please go to Pattern Matching (Java - non-generic).

I use pattern matching a lot in functional languages and find it very natural and readable (to the initiated). Therefore, when I come back to imperative languages such as Java, I find wading through the many nested branching 'if' statements quite horrible and antiquated. For this reason I decided to create a pattern matching class in Java.

Here is an example of how to use the pattern matching class. At the end the 'matchResult' string will equal "43b" because the input parameters match the "with(_, 'b')" method. The underscore character '_' is used as a wildcard.

import com.googlecode.totallylazy.Function2;
import static net.intrepidis.library.functional.PatternMatch._;
import static net.intrepidis.library.functional.PatternMatch.match;
class Example {
    void example() {
        String matchResult =
            match(3, 'b').returns(String.class)
            .with(1, 'a').then(returnWith("1"))
            .with(2, 'b').then(returnWith("2"))
            .with(1, 'b').then(returnWith("3"))
            .with(_, 'b').then(returnWith("4"))
            .with(1, _).then(returnWith("5"))
            .with(_, _).then(returnWith("6"))
            .result();
    }

    private static Function2<Integer, Character, String> returnWith(final String position) {
        return new Function2<Integer, Character, String>() {
            public String call(Integer p1, Character p2) {
                return position + p1 + p2;
            }};
    }
}

Here's another example that uses TotallyLazy to create a function which counts how many vowels are in a string. This is a complicated example and reading more about the TotallyLazy library would help with understanding it.

import com.googlecode.totallylazy.Function1;
import com.googlecode.totallylazy.Callable2;
import static net.intrepidis.library.functional.PatternMatch._;
import static net.intrepidis.library.functional.PatternMatch.match;
class Example {
    void example() {
        Function1<String, Integer[]> countVowels = new Function1<String, Integer[]>() {
            @Override
            public Integer[] call(String str) {
                return characters(str)
                    .fold(new Integer[]{0, 0, 0, 0, 0},
                        new Callable2<Integer[], Character, Integer[]>() {
                            @Override
                            public Integer[] call(final Integer[] acc, Character letter) throws Exception {
                                Integer indexToIncrement =
                                    match(letter).returns(Integer.class)
                                        .with('a').then(0)
                                        .with('e').then(1)
                                        .with('i').then(2)
                                        .with('o').then(3)
                                        .with('u').then(4)
                                        .result();
                                if (indexToIncrement != null)
                                    acc[indexToIncrement]++;
                                return acc;
                            }
                    });
            }
        };
        
        test(numbers(countVowels.call("The quick brown fox jumps over the lazy dog"))).is(1, 3, 1, 4, 2);
    }
}

Below is the full listing of the PatternMatch class:

package net.intrepidis.library.functional;

import com.googlecode.totallylazy.Function1;
import com.googlecode.totallylazy.Function2;
import com.googlecode.totallylazy.Function3;
import com.googlecode.totallylazy.Function4;
import com.googlecode.totallylazy.Function5;
import com.googlecode.totallylazy.Pair;
import com.googlecode.totallylazy.Callable1;

import static com.googlecode.totallylazy.Sequences.sequence;

public class PatternMatch<TR, T1, T2, T3, T4, T5> {
    private boolean matched = false;
    private TR result;
    private final Object[] parameters;

    // Wildcard object matches anything.
    public static final Wildcard _ = new Wildcard();

    private PatternMatch(Object... parameters) {
        this.parameters = parameters;
    }

    public static <U1> PatternMatch<Object, U1, Object, Object, Object, Object> match(U1 p1) {
        return new PatternMatch<Object, U1, Object, Object, Object, Object>(p1);
    }

    public static <U1, U2> PatternMatch<Object, U1, U2, Object, Object, Object> match(U1 p1, U2 p2) {
        return new PatternMatch<Object, U1, U2, Object, Object, Object>(p1, p2);
    }

    public static <U1, U2, U3> PatternMatch<Object, U1, U2, U3, Object, Object> match(U1 p1, U2 p2, U3 p3) {
        return new PatternMatch<Object, U1, U2, U3, Object, Object>(p1, p2, p3);
    }

    public static <U1, U2, U3, U4> PatternMatch<Object, U1, U2, U3, U4, Object> match(U1 p1, U2 p2, U3 p3, U1 p4) {
        return new PatternMatch<Object, U1, U2, U3, U4, Object>(p1, p2, p3, p4);
    }

    public static <U1, U2, U3, U4, U5> PatternMatch<Object, U1, U2, U3, U4, U5> match(U1 p1, U2 p2, U3 p3, U1 p4, U2 p5) {
        return new PatternMatch<Object, U1, U2, U3, U4, U5>(p1, p2, p3, p4, p5);
    }

    @SuppressWarnings("UnusedParameters")
    public <UR> PatternMatch<UR, T1, T2, T3, T4, T5> returns(Class<UR> returnType) {
        return new PatternMatch<UR, T1, T2, T3, T4, T5>(parameters);
    }

    public With1 with(Object p1) {
        return new With1(haveMatch(p1));
    }

    public With2 with(Object p1, Object p2) {
        return new With2(haveMatch(p1, p2));
    }

    public With3 with(Object p1, Object p2, Object p3) {
        return new With3(haveMatch(p1, p2, p3));
    }

    public With4 with(Object p1, Object p2, Object p3, Object p4) {
        return new With4(haveMatch(p1, p2, p3, p4));
    }

    public With5 with(Object p1, Object p2, Object p3, Object p4, Object p5) {
        return new With5(haveMatch(p1, p2, p3, p4, p5));
    }

    public abstract class WithBase {
        protected boolean act = false;

        private WithBase(boolean act) {
            this.act = act;
        }

        public PatternMatch<TR, T1, T2, T3, T4, T5> then(TR returnValue) {
            if (act) {
                result = returnValue;
            }
            return PatternMatch.this;
        }
    }

    public class With1 extends WithBase {
        private With1(boolean act) {
            super(act);
        }

        @SuppressWarnings("unchecked")
        public PatternMatch<TR, T1, T2, T3, T4, T5> then(Function1<T1, TR> actor) throws Exception {
            if (act) {
                result = actor.call(
                        (T1)parameters[0]);
            }
            return PatternMatch.this;
        }
    }

    public class With2 extends WithBase {
        private With2(boolean act) {
            super(act);
        }

        @SuppressWarnings("unchecked")
        public PatternMatch<TR, T1, T2, T3, T4, T5> then(Function2<T1, T2, TR> actor) throws Exception {
            if (act) {
                result = actor.call(
                        (T1)parameters[0],
                        (T2)parameters[1]);
            }
            return PatternMatch.this;
        }
    }

    public class With3 extends WithBase {
        private With3(boolean act) {
            super(act);
        }

        @SuppressWarnings("unchecked")
        public PatternMatch<TR, T1, T2, T3, T4, T5> then(Function3<T1, T2, T3, TR> actor) throws Exception {
            if (act) {
                result = actor.call(
                        (T1)parameters[0],
                        (T2)parameters[1],
                        (T3)parameters[2]);
            }
            return PatternMatch.this;
        }
    }

    public class With4 extends WithBase {
        private With4(boolean act) {
            super(act);
        }

        @SuppressWarnings("unchecked")
        public PatternMatch<TR, T1, T2, T3, T4, T5> then(Function4<T1, T2, T3, T4, TR> actor) throws Exception {
            if (act) {
                result = actor.call(
                        (T1)parameters[0],
                        (T2)parameters[1],
                        (T3)parameters[2],
                        (T4)parameters[3]);
            }
            return PatternMatch.this;
        }
    }

    public class With5 extends WithBase {
        private With5(boolean act) {
            super(act);
        }

        @SuppressWarnings("unchecked")
        public PatternMatch<TR, T1, T2, T3, T4, T5> then(Function5<T1, T2, T3, T4, T5, TR> actor) throws Exception {
            if (act) {
                result = actor.call(
                        (T1)parameters[0],
                        (T2)parameters[1],
                        (T3)parameters[2],
                        (T4)parameters[3],
                        (T5)parameters[4]);
            }
            return PatternMatch.this;
        }
    }

    public TR result() {
        return result;
    }

    public boolean haveMatch(Object... candidates) {
        if (matched) {
            // A match has already been found, so don't match again.
            return false;
        }

        // Are any unmatched?
        boolean areAnyDifferent =
                sequence(candidates) // These possible matches...
                        .zip(sequence(parameters)) // pair up with these inputs...
                        .map(areEqual) // make 'true' if they're equal...
                        .contains(false); // return true on the first non-match.

        // Have all parameters matched?
        return matched = !areAnyDifferent;
    }

    // Wildcard object matches anything.
    public static final class Wildcard {
        private Wildcard() {
        }

        @SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
        @Override
        public boolean equals(Object o) {
            return true;
        }

        @Override
        public String toString() {
            return "Wildcard";
        }
    }

    public static final Callable1<Pair<Object, Object>, Boolean> areEqual = new Callable1<Pair<Object, Object>, Boolean>() {
        public Boolean call(Pair<Object, Object> pair) {
            Object p1 = pair.first();
            Object p2 = pair.second();
            if (p1 == null) {
                return p2 == null;
            }
            return p1.equals(p2);
        }
    };
}

This code is completely free to use by anybody. It is held under the Do What You Want To Public License: http://tinyurl.com/DWYWTPL