1 module testrunner; 2 3 import std.meta : AliasSeq; 4 import std.traits; 5 import std.stdio; 6 7 /** 8 * When `runTests` is called on an object, methods annotated with `@Test` will be executed 9 * after `@BeforeClass` methods, but before `@AfterClass` methods. The name of a test will 10 * be displayed during execution. 11 */ 12 struct Test { 13 string name; 14 } 15 16 /** 17 * When `runTests` is called on an object, methods annotaetd with `@BeforeClass` will be executed 18 * before any other methods. These methods typically set up test resources. 19 */ 20 struct BeforeClass { } 21 22 /** 23 * When `runTests` is called on an object, methods annotaetd with `@BeforeClass` will be executed 24 * before any other methods. These methdos typically tear down test resources. 25 */ 26 struct AfterClass { } 27 28 /** 29 * Scans a class and executes methods in the following order. 30 * 1. Class `@BeforeClass` methods. 31 * 2. Base class `@BeforeClass` methods. 32 * 3. Class `@Test` methods. 33 * 4. Base class `@Test` methods. 34 * 5. Class `@AfterClass` methods. 35 * 6. Base class `@AfterClass` methods. 36 */ 37 void runTests(TestClass)(TestClass testClass) { 38 // Search for member functions annotated with @BeforeClass and run them. 39 static foreach (alias method; getSymbolsByUDA!(TestClass, BeforeClass)) { 40 __traits(getMember, testClass, __traits(identifier, method))(); 41 } 42 43 // Search for member functions annotated with @Test and run them. 44 static foreach (alias method; getSymbolsByUDA!(TestClass, Test)) { 45 writeln("▶ Running Test: ", getUDAs!(method, Test)[0].name); 46 __traits(getMember, testClass, __traits(identifier, method))(); 47 } 48 49 // Search for member functions annotated with @AfterClass and run them. 50 static foreach (alias method; getSymbolsByUDA!(TestClass, AfterClass)) { 51 __traits(getMember, testClass, __traits(identifier, method))(); 52 } 53 } 54 55 unittest { 56 int[] testOrder; 57 58 class DummyTest { 59 @BeforeClass 60 void fanny() { 61 testOrder ~= 2; 62 } 63 64 @Test("fanny test") 65 void mytest() { 66 testOrder ~= 4; 67 } 68 69 @AfterClass 70 void foony() { 71 testOrder ~= 6; 72 } 73 } 74 75 class DoomyTest : DummyTest { 76 @BeforeClass 77 void flam() { 78 testOrder ~= 1; 79 } 80 @Test("flam test") 81 void moytest() { 82 testOrder ~= 3; 83 } 84 @AfterClass 85 void flom() { 86 testOrder ~= 5; 87 } 88 } 89 90 runTests(new DoomyTest()); 91 assert(testOrder == [1, 2, 3, 4, 5, 6]); 92 }