/** A quick & dirty unit test framework inspired by Ruby's Unit::Test Written in the D programming language 2.0 Authors: Wei Li (oldrev@gmail.com) License: BSD Copyright: Copyright (C) 2007 by Wei Li. All rights reserved. */ import std.stdio; //////////////////////////////////////////////////////////////////////////////// struct Failure { string location; string message; string testName; } //////////////////////////////////////////////////////////////////////////////// struct Error { Exception exception; string testName; } //////////////////////////////////////////////////////////////////////////////// class TestResult { private Error[] m_errors; private Failure[] m_fails; private int m_runCount; private int m_assertionCount; private int m_testCount; Error[] errors() { return m_errors; } Failure[] failures() { return m_fails; } void addFailure(const string loc, const string msg, const string name) { Failure f; with(f) { location = loc; message = msg; testName = name; } m_fails ~= f; } void addError(Exception ex, const string name) { Error e; with(e) { exception = ex; testName = name; } m_errors ~= e; } void addAssertion() { m_assertionCount++; } void addTest() { m_testCount++; } void addRun() { m_runCount++; } bool hasPassed() { return m_errors.length == 0 && m_fails.length == 0; } int errorCount() { return cast(int)m_errors.length; } int failureCount() { return cast(int)m_fails.length; } int runCount() { return m_runCount; } int testCount() { return m_testCount; } int assertionCount() { return m_assertionCount; } } //////////////////////////////////////////////////////////////////////////////// abstract class TestBase { protected this() { } abstract void run(TestResult result); abstract const bool isRunning(); } //////////////////////////////////////////////////////////////////////////////// abstract class TestCase(T) : TestBase { alias T Subclass; struct TestMethod { string name; void delegate() method; } public const string name = Subclass.classinfo.name; private TestResult m_result; private TestMethod[] m_methods; private size_t m_currentMethod; private bool m_running = false; this() { } private static string ctfMakeString(T)() { string ret; foreach(str; __traits(allMembers, T)) { if(str[0..4] == "test") ret ~= `addTestMethod(TestMethod("` ~ str ~ `", &sc.` ~ str ~ `)); ` ~ "\n"; } return ret; } public void initial(const Subclass sc) { mixin(ctfMakeString!(Subclass)()); } void addTestMethod(TestMethod tm) { m_methods ~= tm; } static Subclass createChild() { auto o = new Subclass; o.initial(o); return o; } void setup() {} void teardown() {} override const bool isRunning() { return m_running; } override void run(TestResult result) { m_result = result; foreach(size_t i, TestMethod tm; m_methods) { try { m_currentMethod = i; m_result.addTest(); setup(); m_running = true; tm.method(); m_running = false; teardown(); } catch(Exception ex) { m_result.addError(ex, currentMethodName); } } } const string currentMethodName() { return name ~ "." ~ m_methods[m_currentMethod].name; } void assertTrue(bool x, string message = null) { m_result.addAssertion(); if(!x) { m_result.addFailure(name, message, currentMethodName); } } } //////////////////////////////////////////////////////////////////////////////// class TestSuit(Subclass, Tests...) : TestBase { public const string name = Subclass.classinfo.name; private TestBase[] m_tests; private bool m_running = false; this() { m_running = false; foreach(T; Tests) { T test = T.createChild(); addTest(test); } } static Subclass createChild() { return new Subclass; } const(TestBase)[] tests() { return m_tests; } void addTest(TestBase tb) in { assert(tb !is null); } body { m_tests ~= tb; } const bool empty() { return Tests.length == 0; } override const bool isRunning() { return m_running; } override void run(TestResult result) { m_running = true; foreach(test; m_tests) { test.run(result); } m_running = false; } } class ConsoleRunner { static void showFailures(TestResult tr) { foreach(size_t i, fail; tr.failures){ writefln("%d) Failure:", i); writefln("\tTest: %s", fail.testName); writefln("\tMessage: %s", fail.message); writefln("\tLocation: %s", fail.location); writefln(); } } static void run(TestBase tb) { auto result = new TestResult; tb.run(result); showFailures(result); writefln(); writefln("%d tests, %d assertions, %d failures, %d errors", result.testCount, result.assertionCount, result.failureCount, result.errorCount); } } //////////////////////////////////////////////////////////////////////////////// class MyTestCase : TestCase!(MyTestCase) { this() { } void testOne() { assertTrue(false, "A stupid assertion"); assertTrue(true); assertTrue(true); throw new Exception("Exception raised"); } void testTwo() { assertTrue(true); } void testThree() { assertTrue(true); } } class MyTestCase2 : TestCase!(MyTestCase2) { this() { } void testOne() { assertTrue(true); } void testTwo() { assertTrue(true); } void testThree() { assertTrue(false, "Yet another stupid assertion"); } } class MyTestSuit1: TestSuit!(MyTestSuit1, MyTestCase) { } class MyTestSuit2: TestSuit!(MyTestSuit2, MyTestCase2) { } class MyTestSuit3: TestSuit!(MyTestSuit3, MyTestSuit1, MyTestSuit2) { } void main() { auto ts = new MyTestSuit3; ConsoleRunner.run(ts); }