1 /* 2 * Hunt - A refined core library for D programming language. 3 * 4 * Copyright (C) 2018-2019 HuntLabs 5 * 6 * Website: https://www.huntlabs.net/ 7 * 8 * Licensed under the Apache-2.0 License. 9 * 10 */ 11 12 module hunt.util.UnitTest; 13 14 import hunt.util.ObjectUtils; 15 import core.time; 16 17 void testUnits(T)() { 18 enum v = generateUnitTests!T; 19 // pragma(msg, v); 20 mixin(v); 21 } 22 23 string generateUnitTests(T)() { 24 import std.string; 25 import std.algorithm; 26 import std.traits; 27 28 enum fullTypeName = fullyQualifiedName!(T); 29 enum memberModuleName = moduleName!(T); 30 31 string[] methodsBefore; 32 string[] methodsAfter; 33 34 string str; 35 str ~= `import std.stdio; import hunt.logging.ConsoleLogger; 36 writeln("================================="); 37 writeln("Module: ` ~ fullTypeName ~ ` "); 38 writeln("================================="); 39 40 `; 41 str ~= "import " ~ memberModuleName ~ ";\n"; 42 str ~= T.stringof ~ " t;\n"; 43 44 // 45 foreach (memberName; __traits(allMembers, T)) { 46 // pragma(msg, "member: " ~ memberName); 47 static if (is(T == class) && FixedObjectMembers.canFind(memberName)) { 48 // pragma(msg, "skipping fixed Object member: " ~ memberName); 49 } else { 50 enum memberProtection = __traits(getProtection, __traits(getMember, T, memberName)); 51 static if (memberProtection == "private" 52 || memberProtection == "protected" 53 || memberProtection == "export") { 54 // version (HUNT_DEBUG) pragma(msg, "skip private member: " ~ memberName); 55 } else { 56 import std.meta : Alias; 57 alias currentMember = Alias!(__traits(getMember, T, memberName)); 58 static if (hasUDA!(currentMember, Before)) { 59 alias memberType = typeof(currentMember); 60 static if (is(memberType == function)) { 61 methodsBefore ~= memberName; 62 } 63 } 64 65 static if (hasUDA!(currentMember, After)) { 66 alias memberType = typeof(currentMember); 67 static if (is(memberType == function)) { 68 methodsAfter ~= memberName; 69 } 70 } 71 } 72 } 73 } 74 75 // 76 foreach (memberName; __traits(allMembers, T)) { 77 // pragma(msg, "member: " ~ memberName); 78 static if (is(T == class) && FixedObjectMembers.canFind(memberName)) { 79 // pragma(msg, "skipping fixed Object member: " ~ memberName); 80 } else { 81 enum memberProtection = __traits(getProtection, __traits(getMember, T, memberName)); 82 static if (memberProtection == "private" 83 || memberProtection == "protected" 84 || memberProtection == "export") { 85 // version (HUNT_DEBUG) pragma(msg, "skip private member: " ~ memberName); 86 } else { 87 import std.meta : Alias; 88 alias currentMember = Alias!(__traits(getMember, T, memberName)); 89 90 static if (isFunction!(currentMember)) { 91 alias testWithUDAs = getUDAs!(currentMember, TestWith);// hasUDA!(currentMember, Test); 92 static if(testWithUDAs.length >0) { 93 str ~= `writeln("\n========> testing: ` ~ memberName ~ "\");\n"; 94 95 // Every @Test method will be test alone. 96 str ~= "t = new " ~ T.stringof ~ "();\n"; 97 98 // running methods annotated with BEFORE 99 foreach(string s; methodsBefore) { 100 str ~= "t." ~ s ~ "();\n"; 101 } 102 103 // execute a test 104 alias expectedType = typeof(testWithUDAs[0].expected); 105 static if(is(expectedType : Throwable)) { 106 str ~= "try { t." ~ memberName ~ "(); } catch(" ~ fullyQualifiedName!expectedType ~ 107 " ex) { version(HUNT_DEBUG) { warning(ex.msg); } }\n"; 108 } else { 109 str ~= "t." ~ memberName ~ "();\n"; 110 } 111 112 // running methods annotated with BEFORE 113 foreach(string s; methodsAfter) { 114 str ~= "t." ~ s ~ "();\n"; 115 } 116 } else { 117 static if (memberName.startsWith("test") || memberName.endsWith("Test") 118 || hasUDA!(currentMember, Test)) { 119 str ~= `writeln("\n========> testing: ` ~ memberName ~ "\");\n"; 120 121 // Every @Test method will be test alone. 122 str ~= "t = new " ~ T.stringof ~ "();\n"; 123 124 // running methods annotated with BEFORE 125 foreach(string s; methodsBefore) { 126 str ~= "t." ~ s ~ "();\n"; 127 } 128 129 // execute a test 130 str ~= "t." ~ memberName ~ "();\n"; 131 132 // running methods annotated with BEFORE 133 foreach(string s; methodsAfter) { 134 str ~= "t." ~ s ~ "();\n"; 135 } 136 } 137 } 138 } 139 } 140 } 141 } 142 return str; 143 } 144 145 /** 146 */ 147 struct TestWith(T = Object) { 148 T expected; 149 } 150 151 152 /** 153 * The <code>Test</code> annotation tells JUnit that the <code>public void</code> method 154 * to which it is attached can be run as a test case. To run the method, 155 * JUnit first constructs a fresh instance of the class then invokes the 156 * annotated method. Any exceptions thrown by the test will be reported 157 * by JUnit as a failure. If no exceptions are thrown, the test is assumed 158 * to have succeeded. 159 * <p> 160 * A simple test looks like this: 161 * <pre> 162 * public class Example { 163 * <b>@Test</b> 164 * public void method() { 165 * org.junit.Assert.assertTrue( new ArrayList().isEmpty() ); 166 * } 167 * } 168 * </pre> 169 * <p> 170 * The <code>Test</code> annotation supports two optional parameters. 171 * The first, <code>expected</code>, declares that a test method should throw 172 * an exception. If it doesn't throw an exception or if it throws a different exception 173 * than the one declared, the test fails. For example, the following test succeeds: 174 * <pre> 175 * @Test(<b>expected=IndexOutOfBoundsException.class</b>) public void outOfBounds() { 176 * new ArrayList<Object>().get(1); 177 * } 178 * </pre> 179 * If the exception's message or one of its properties should be verified, the 180 * {@link org.junit.rules.ExpectedException ExpectedException} rule can be used. Further 181 * information about exception testing can be found at the 182 * <a href="https://github.com/junit-team/junit/wiki/Exception-testing">JUnit Wiki</a>. 183 * <p> 184 * The second optional parameter, <code>timeout</code>, causes a test to fail if it takes 185 * longer than a specified amount of clock time (measured in milliseconds). The following test fails: 186 * <pre> 187 * @Test(<b>timeout=100</b>) public void infinity() { 188 * while(true); 189 * } 190 * </pre> 191 * <b>Warning</b>: while <code>timeout</code> is useful to catch and terminate 192 * infinite loops, it should <em>not</em> be considered deterministic. The 193 * following test may or may not fail depending on how the operating system 194 * schedules threads: 195 * <pre> 196 * @Test(<b>timeout=100</b>) public void sleep100() { 197 * Thread.sleep(100); 198 * } 199 * </pre> 200 * <b>THREAD SAFETY WARNING:</b> Test methods with a timeout parameter are run in a thread other than the 201 * thread which runs the fixture's @Before and @After methods. This may yield different behavior for 202 * code that is not thread safe when compared to the same test method without a timeout parameter. 203 * <b>Consider using the {@link org.junit.rules.Timeout} rule instead</b>, which ensures a test method is run on the 204 * same thread as the fixture's @Before and @After methods. 205 * 206 */ 207 struct Test { 208 Duration timeout; 209 } 210 211 212 /** 213 * When writing tests, it is common to find that several tests need similar 214 * objects created before they can run. Annotating a <code>public void</code> method 215 * with <code>@Before</code> causes that method to be run before the {@link org.junit.Test} method. 216 * The <code>@Before</code> methods of superclasses will be run before those of the current class, 217 * unless they are overridden in the current class. No other ordering is defined. 218 * <p> 219 * Here is a simple example: 220 * <pre> 221 * public class Example { 222 * List empty; 223 * @Before public void initialize() { 224 * empty= new ArrayList(); 225 * } 226 * @Test public void size() { 227 * ... 228 * } 229 * @Test public void remove() { 230 * ... 231 * } 232 * } 233 * </pre> 234 * 235 */ 236 interface Before { 237 } 238 239 240 /** 241 * If you allocate external resources in a {@link org.junit.Before} method you need to release them 242 * after the test runs. Annotating a <code>public void</code> method 243 * with <code>@After</code> causes that method to be run after the {@link org.junit.Test} method. All <code>@After</code> 244 * methods are guaranteed to run even if a {@link org.junit.Before} or {@link org.junit.Test} method throws an 245 * exception. The <code>@After</code> methods declared in superclasses will be run after those of the current 246 * class, unless they are overridden in the current class. 247 * <p> 248 * Here is a simple example: 249 * <pre> 250 * public class Example { 251 * File output; 252 * @Before public void createOutputFile() { 253 * output= new File(...); 254 * } 255 * @Test public void something() { 256 * ... 257 * } 258 * @After public void deleteOutputFile() { 259 * output.delete(); 260 * } 261 * } 262 * </pre> 263 * 264 */ 265 interface After { 266 } 267 268 269 /** 270 * Sometimes several tests need to share computationally expensive setup 271 * (like logging into a database). While this can compromise the independence of 272 * tests, sometimes it is a necessary optimization. Annotating a <code>public static void</code> no-arg method 273 * with <code>@BeforeClass</code> causes it to be run once before any of 274 * the test methods in the class. The <code>@BeforeClass</code> methods of superclasses 275 * will be run before those of the current class, unless they are shadowed in the current class. 276 * <p> 277 * For example: 278 * <pre> 279 * public class Example { 280 * @BeforeClass public static void onlyOnce() { 281 * ... 282 * } 283 * @Test public void one() { 284 * ... 285 * } 286 * @Test public void two() { 287 * ... 288 * } 289 * } 290 * </pre> 291 * 292 */ 293 interface BeforeClass { 294 295 } 296 297 298 /** 299 * If you allocate expensive external resources in a {@link org.junit.BeforeClass} method you need to release them 300 * after all the tests in the class have run. Annotating a <code>public static void</code> method 301 * with <code>@AfterClass</code> causes that method to be run after all the tests in the class have been run. All <code>@AfterClass</code> 302 * methods are guaranteed to run even if a {@link org.junit.BeforeClass} method throws an 303 * exception. The <code>@AfterClass</code> methods declared in superclasses will be run after those of the current 304 * class, unless they are shadowed in the current class. 305 * <p> 306 * Here is a simple example: 307 * <pre> 308 * public class Example { 309 * private static DatabaseConnection database; 310 * @BeforeClass public static void login() { 311 * database= ...; 312 * } 313 * @Test public void something() { 314 * ... 315 * } 316 * @Test public void somethingElse() { 317 * ... 318 * } 319 * @AfterClass public static void logout() { 320 * database.logout(); 321 * } 322 * } 323 * </pre> 324 * 325 */ 326 interface AfterClass { 327 328 } 329 330 331 /** 332 * Sometimes you want to temporarily disable a test or a group of tests. Methods annotated with 333 * {@link org.junit.Test} that are also annotated with <code>@Ignore</code> will not be executed as tests. 334 * Also, you can annotate a class containing test methods with <code>@Ignore</code> and none of the containing 335 * tests will be executed. Native JUnit 4 test runners should report the number of ignored tests along with the 336 * number of tests that ran and the number of tests that failed. 337 * 338 * <p>For example: 339 * <pre> 340 * @Ignore @Test public void something() { ... 341 * </pre> 342 * @Ignore takes an optional default parameter if you want to record why a test is being ignored: 343 * <pre> 344 * @Ignore("not ready yet") @Test public void something() { ... 345 * </pre> 346 * @Ignore can also be applied to the test class: 347 * <pre> 348 * @Ignore public class IgnoreMe { 349 * @Test public void test1() { ... } 350 * @Test public void test2() { ... } 351 * } 352 * </pre> 353 * 354 */ 355 struct Ignore { 356 /** 357 * The optional reason why the test is ignored. 358 */ 359 string value; 360 }