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 }