1 // Copyright (c) 2017-2018 Matthew Brennan Jones <matthew.brennan.jones@gmail.com> 2 // Boost Software License - Version 1.0 3 // Behavior Driven Development for the D programming language 4 // https://github.com/workhorsy/BDD 5 6 /++ 7 Behavior Driven Development for the D programming language 8 9 Home page: 10 $(LINK https://github.com/workhorsy/BDD) 11 12 Version: 1.3.0 13 14 License: 15 Boost Software License - Version 1.0 16 17 Examples: 18 ---- 19 import BDD; 20 21 int add(int a, int b) { 22 return a + b; 23 } 24 25 unittest { 26 describe("math#add", 27 it("Should add positive numbers", delegate() { 28 add(5, 7).shouldEqual(12); 29 }), 30 it("Should add negative numbers", delegate() { 31 add(5, -7).shouldEqual(-2); 32 }) 33 ); 34 } 35 36 // Prints the results of the tests 37 int main() { 38 return BDD.printResults(); 39 } 40 ---- 41 +/ 42 43 44 module BDD; 45 46 47 /++ 48 Used to assert that one value is equal to another value. 49 50 Params: 51 a = The value to test. 52 b = The value it should be equal to. 53 file = The file name that the assert failed in. Should be left as default. 54 line = The file line that the assert failed in. Should be left as default. 55 56 Throws: 57 If values are not equal, will throw an AssertError with expected and actual 58 values. 59 60 Examples: 61 ---- 62 // Will throw an exception like "AssertError@example.d(6): <3> expected to equal <5>." 63 int z = 3; 64 z.shouldEqual(5); 65 ---- 66 +/ 67 void shouldEqual(T, U)(T a, U b, string message=null, string file=__FILE__, size_t line=__LINE__) { 68 import std..string : format; 69 import std.array : replace; 70 import core.exception : AssertError; 71 72 if (a != b) { 73 if (! message) { 74 message = "<%s> expected to equal <%s>.".format(a, b); 75 } 76 message = message.replace("\r", "\\r").replace("\n", "\\n"); 77 throw new AssertError(message, file, line); 78 } 79 } 80 81 unittest { 82 describe("BDD#shouldEqual", 83 it("Should succeed when equal", delegate() { 84 "abc".shouldEqual("abc"); 85 }), 86 it("Should fail when NOT equal", delegate() { 87 shouldThrow(delegate() { 88 "abc".shouldEqual("xyz"); 89 }, "<abc> expected to equal <xyz>."); 90 }) 91 ); 92 } 93 94 /++ 95 Used to assert that one value is NOT equal to another value. 96 97 Params: 98 a = The value to test. 99 b = The value it should NOT be equal to. 100 file = The file name that the assert failed in. Should be left as default. 101 line = The file line that the assert failed in. Should be left as default. 102 103 Throws: 104 If values are NOT equal, will throw an AssertError with unexpected and 105 actual values. 106 107 Examples: 108 ---- 109 // Will throw an exception like "AssertError@example.d(6): <3> expected to NOT equal <3>." 110 int z = 3; 111 z.shouldNotEqual(3); 112 ---- 113 +/ 114 void shouldNotEqual(T, U)(T a, U b, string message=null, string file=__FILE__, size_t line=__LINE__) { 115 import std..string : format; 116 import std.array : replace; 117 import core.exception : AssertError; 118 119 if (a == b) { 120 if (! message) { 121 message = "<%s> expected to NOT equal <%s>.".format(a, b); 122 } 123 message = message.replace("\r", "\\r").replace("\n", "\\n"); 124 throw new AssertError(message, file, line); 125 } 126 } 127 128 unittest { 129 describe("BDD#shouldNotEqual", 130 it("Should succeed when NOT equal", delegate() { 131 "abc".shouldNotEqual("xyz"); 132 }), 133 it("Should fail when equal", delegate() { 134 shouldThrow(delegate() { 135 "abc".shouldNotEqual("abc"); 136 }, "<abc> expected to NOT equal <abc>."); 137 }) 138 ); 139 } 140 141 /++ 142 Used to assert that one value is equal to null. 143 144 Params: 145 a = The value that should equal null. 146 file = The file name that the assert failed in. Should be left as default. 147 line = The file line that the assert failed in. Should be left as default. 148 149 Throws: 150 If value is NOT null, will throw an AssertError. 151 152 Examples: 153 ---- 154 // Will throw an exception like "AssertError@example.d(6): expected to be <null>." 155 string z = "blah"; 156 z.shouldBeNull(); 157 ---- 158 +/ 159 void shouldBeNull(T)(T a, string message=null, string file=__FILE__, size_t line=__LINE__) { 160 import std..string : format; 161 import std.array : replace; 162 import core.exception : AssertError; 163 164 if (a !is null) { 165 if (! message) { 166 message = "expected to be <null>."; 167 } 168 message = message.replace("\r", "\\r").replace("\n", "\\n"); 169 throw new AssertError(message, file, line); 170 } 171 } 172 173 unittest { 174 describe("BDD#shouldBeNull", 175 it("Should succeed when it is null", delegate() { 176 string value = null; 177 value.shouldBeNull(); 178 }), 179 it("Should fail when it is NOT null", delegate() { 180 shouldThrow(delegate() { 181 string value = "abc"; 182 value.shouldBeNull(); 183 }, "expected to be <null>."); 184 }) 185 ); 186 } 187 188 /++ 189 Used to assert that one value is NOT equal to null. 190 191 Params: 192 a = The value that should NOT equal null. 193 file = The file name that the assert failed in. Should be left as default. 194 line = The file line that the assert failed in. Should be left as default. 195 196 Throws: 197 If value is null, will throw an AssertError. 198 199 Examples: 200 ---- 201 // Will throw an exception like "AssertError@example.d(6): expected to NOT be <null>." 202 string z = null; 203 z.shouldNotBeNull(); 204 ---- 205 +/ 206 void shouldNotBeNull(T)(T a, string message=null, string file=__FILE__, size_t line=__LINE__) { 207 import core.exception : AssertError; 208 209 if (a is null) { 210 if (! message) { 211 message = "expected to NOT be <null>."; 212 } 213 throw new AssertError(message, file, line); 214 } 215 } 216 217 unittest { 218 describe("BDD#shouldNotBeNull", 219 it("Should succeed when it is NOT null", delegate() { 220 "abc".shouldNotBeNull(); 221 }), 222 it("Should fail when it is null", delegate() { 223 shouldThrow(delegate() { 224 string value = null; 225 value.shouldNotBeNull(); 226 }, "expected to NOT be <null>."); 227 }) 228 ); 229 } 230 231 /++ 232 Used to assert that one value is in an array of specified values. 233 234 Params: 235 value = The value to test. 236 valid_values = An array of valid values. 237 file = The file name that the assert failed in. Should be left as default. 238 line = The file line that the assert failed in. Should be left as default. 239 240 Throws: 241 If the value is not in the array, will throw an AssertError with the value 242 and array values. 243 244 Examples: 245 ---- 246 // Will throw an exception like "AssertError@example.d(6): <Bobrick> is not in <[Tim, Al]>." 247 "Bobrick".shouldBeIn(["Tim", "Al"]); 248 ---- 249 +/ 250 void shouldBeIn(T, U)(T value, U[] valid_values, string file=__FILE__, size_t line=__LINE__) { 251 import std..string : format; 252 import std.array : replace, join; 253 import core.exception : AssertError; 254 255 bool is_valid = false; 256 257 foreach (valid; valid_values) { 258 if (value == valid) { 259 is_valid = true; 260 } 261 } 262 263 if (! is_valid) { 264 string message = "<%s> is not in <[%s]>.".format(value, valid_values.join(", ")); 265 message = message.replace("\r", "\\r").replace("\n", "\\n"); 266 throw new AssertError(message, file, line); 267 } 268 } 269 270 unittest { 271 describe("BDD#shouldBeIn", 272 it("Should succeed when the string is in the array", delegate() { 273 "abc".shouldBeIn(["abc", "xyz"]); 274 }), 275 it("Should fail when the string is NOT in the array", delegate() { 276 shouldThrow(delegate() { 277 "qed".shouldBeIn(["abc", "xyz"]); 278 }, "<qed> is not in <[abc, xyz]>."); 279 }) 280 ); 281 } 282 283 /++ 284 Used to assert that one value is greater than another value. 285 286 Params: 287 a = The value to test. 288 b = The value it should be greater than. 289 file = The file name that the assert failed in. Should be left as default. 290 line = The file line that the assert failed in. Should be left as default. 291 292 Throws: 293 If the value is NOT greater, will throw an AssertError with expected and actual 294 values. 295 296 Examples: 297 ---- 298 // Will throw an exception like "AssertError@example.d(6): <5> expected to be greater than <10>." 299 5.shouldBeGreater(10); 300 ---- 301 +/ 302 void shouldBeGreater(T, U)(T a, U b, string message=null, string file=__FILE__, size_t line=__LINE__) { 303 import std..string : format; 304 import std.array : replace; 305 import core.exception : AssertError; 306 307 if (a <= b) { 308 if (! message) { 309 message = "<%s> expected to be greater than <%s>.".format(a, b); 310 } 311 message = message.replace("\r", "\\r").replace("\n", "\\n"); 312 throw new AssertError(message, file, line); 313 } 314 } 315 316 unittest { 317 describe("BDD#shouldBeGreater", 318 it("Should succeed when one is greater", delegate() { 319 10.shouldBeGreater(5); 320 }), 321 it("Should fail when one is NOT greater", delegate() { 322 shouldThrow(delegate() { 323 5.shouldBeGreater(10); 324 }, "<5> expected to be greater than <10>."); 325 }) 326 ); 327 } 328 329 /++ 330 Used to assert that one value is less than another value. 331 332 Params: 333 a = The value to test. 334 b = The value it should be less than. 335 file = The file name that the assert failed in. Should be left as default. 336 line = The file line that the assert failed in. Should be left as default. 337 338 Throws: 339 If the value is NOT less, will throw an AssertError with expected and actual 340 values. 341 342 Examples: 343 ---- 344 // Will throw an exception like "AssertError@example.d(6): <10> expected to be less than <5>." 345 10.shouldBeLess(5); 346 ---- 347 +/ 348 void shouldBeLess(T, U)(T a, U b, string message=null, string file=__FILE__, size_t line=__LINE__) { 349 import std..string : format; 350 import std.array : replace; 351 import core.exception : AssertError; 352 353 if (a >= b) { 354 if (! message) { 355 message = "<%s> expected to be less than <%s>.".format(a, b); 356 } 357 message = message.replace("\r", "\\r").replace("\n", "\\n"); 358 throw new AssertError(message, file, line); 359 } 360 } 361 362 unittest { 363 describe("BDD#shouldBeLess", 364 it("Should succeed when one is less", delegate() { 365 5.shouldBeLess(10); 366 }), 367 it("Should fail when one is NOT less", delegate() { 368 shouldThrow(delegate() { 369 10.shouldBeLess(5); 370 }, "<10> expected to be less than <5>."); 371 }) 372 ); 373 } 374 375 /++ 376 Used to assert that one value is greater or equal than another value. 377 378 Params: 379 a = The value to test. 380 b = The value it should be greater or equal than. 381 file = The file name that the assert failed in. Should be left as default. 382 line = The file line that the assert failed in. Should be left as default. 383 384 Throws: 385 If the value is NOT greater or equal, will throw an AssertError with expected and actual 386 values. 387 388 Examples: 389 ---- 390 // Will throw an exception like "AssertError@example.d(6): <5> expected to be greater or equal to <10>." 391 5.shouldBeGreaterOrEqual(10); 392 ---- 393 +/ 394 void shouldBeGreaterOrEqual(T, U)(T a, U b, string message=null, string file=__FILE__, size_t line=__LINE__) { 395 import std..string : format; 396 import std.array : replace; 397 import core.exception : AssertError; 398 399 if (a < b) { 400 if (! message) { 401 message = "<%s> expected to be greater or equal to <%s>.".format(a, b); 402 } 403 message = message.replace("\r", "\\r").replace("\n", "\\n"); 404 throw new AssertError(message, file, line); 405 } 406 } 407 408 unittest { 409 describe("BDD#shouldBeGreaterOrEqual", 410 it("Should succeed when one is greater", delegate() { 411 10.shouldBeGreaterOrEqual(5); 412 }), 413 it("Should succeed when one is equal", delegate() { 414 10.shouldBeGreaterOrEqual(10); 415 }), 416 it("Should fail when one is less", delegate() { 417 shouldThrow(delegate() { 418 5.shouldBeGreaterOrEqual(10); 419 }, "<5> expected to be greater or equal to <10>."); 420 }) 421 ); 422 } 423 424 /++ 425 Used to assert that one value is less or equal than another value. 426 427 Params: 428 a = The value to test. 429 b = The value it should be less or equal than. 430 file = The file name that the assert failed in. Should be left as default. 431 line = The file line that the assert failed in. Should be left as default. 432 433 Throws: 434 If the value is NOT less or equal, will throw an AssertError with expected and actual 435 values. 436 437 Examples: 438 ---- 439 // Will throw an exception like "AssertError@example.d(6): <10> expected to be less or equal to <5>." 440 10.shouldBeLessOrEqual(5); 441 ---- 442 +/ 443 void shouldBeLessOrEqual(T, U)(T a, U b, string message=null, string file=__FILE__, size_t line=__LINE__) { 444 import std..string : format; 445 import std.array : replace; 446 import core.exception : AssertError; 447 448 if (a > b) { 449 if (! message) { 450 message = "<%s> expected to be less or equal to <%s>.".format(a, b); 451 } 452 message = message.replace("\r", "\\r").replace("\n", "\\n"); 453 throw new AssertError(message, file, line); 454 } 455 } 456 457 unittest { 458 describe("BDD#shouldBeLessOrEqual", 459 it("Should succeed when one is less", delegate() { 460 5.shouldBeLessOrEqual(10); 461 }), 462 it("Should succeed when one is equal", delegate() { 463 10.shouldBeLessOrEqual(10); 464 }), 465 it("Should fail when one is less", delegate() { 466 shouldThrow(delegate() { 467 10.shouldBeLessOrEqual(5); 468 }, "<10> expected to be less or equal to <5>."); 469 }) 470 ); 471 } 472 473 /++ 474 Used for asserting that a delegate will throw an exception. 475 476 Params: 477 cb = The delegate that is expected to throw the exception. 478 message = The message that is expected to be in the exception. Will not 479 be tested, if it is null. 480 file = The file name that the assert failed in. Should be left as default. 481 line = The file line that the assert failed in. Should be left as default. 482 483 Throws: 484 If delegate does NOT throw, will throw an AssertError. 485 486 Examples: 487 ---- 488 // Makes sure it throws with the message "boom!" 489 shouldThrow(delegate() { 490 throw new Exception("boom!"); 491 }, "boom!"); 492 493 // Makes sure it throws, but does not check the message 494 shouldThrow(delegate() { 495 throw new Exception("boom!"); 496 }); 497 498 // Will throw an exception like "AssertError@test/example.d(7): Exception was not thrown. Expected: boom!" 499 shouldThrow(delegate() { 500 501 }, "boom!"); 502 503 // Will throw an exception like "AssertError@test/example.d(7): Exception was not thrown. Expected one. 504 shouldThrow(delegate() { 505 506 }); 507 ---- 508 +/ 509 void shouldThrow(void delegate() cb, string message=null, string file=__FILE__, size_t line=__LINE__) { 510 import core.exception : AssertError; 511 512 bool has_thrown = false; 513 try { 514 cb(); 515 } catch (Throwable ex) { 516 has_thrown = true; 517 if (message && message != ex.msg) { 518 throw new AssertError("Exception was thrown. But expected: " ~ message, file, line); 519 } 520 } 521 522 if (! has_thrown) { 523 if (message) { 524 throw new AssertError("Exception was not thrown. Expected: " ~ message, file, line); 525 } else { 526 throw new AssertError("Exception was not thrown. Expected one.", file, line); 527 } 528 } 529 } 530 531 unittest { 532 describe("BDD#shouldThrow", 533 it("Should succeed when an exception is thrown", delegate() { 534 bool has_thrown = false; 535 536 shouldThrow(delegate() { 537 has_thrown = true; 538 throw new Exception("boom!"); 539 }); 540 541 has_thrown.shouldEqual(true); 542 }), 543 it("Should fail when an exception is not thrown", delegate() { 544 Throwable ex = null; 545 try { 546 shouldThrow(delegate() { 547 // Does not throw 548 }); 549 } catch (Throwable exception) { 550 ex = exception; 551 } 552 553 ex.shouldNotBeNull(); 554 ex.msg.shouldEqual("Exception was not thrown. Expected one."); 555 }) 556 ); 557 } 558 559 void beforeIt(void delegate() cb) { 560 _before_it = cb; 561 } 562 563 void afterIt(void delegate() cb) { 564 _after_it = cb; 565 } 566 567 568 /++ 569 Prints the results of all the tests. Returns 0 if all tests pass, or 1 if any fail. 570 571 Examples: 572 ---- 573 BDD.printResults(); 574 ---- 575 576 Output: 577 ---- 578 Unit Test Results: 579 4 total, 2 successful, 2 failed 580 math#add 581 - "5 + 7 = 12: <12> expected to equal <123>." broken_math.d(2) 582 math#subtract 583 - "5 - 7 = -2: <-2> expected to equal <456>." broken_math.d(6) 584 ---- 585 +/ 586 int printResults() { 587 import std.stdio : stdout; 588 589 stdout.writeln("Unit Test Results:"); 590 stdout.writefln("%d total, %d successful, %d failed", _success_count + _fail_count, _success_count, _fail_count); 591 592 foreach (a, b; _fail_messages) { 593 stdout.writefln("%s", a); 594 foreach (c; b) { 595 stdout.writefln("- %s", c); 596 } 597 } 598 599 return _fail_count > 0; 600 } 601 602 /++ 603 The message is usually the name of the thing being tested. 604 605 Params: 606 describe_message = The thing that is being described. 607 pairs = All the 'it' delegate functions that will test the thing. 608 609 Examples: 610 ---- 611 describe("example_library#thing_to_test", 612 it("Should NOT fail", delegate() { 613 // code here 614 }) 615 ); 616 ---- 617 +/ 618 void describe(TestPair...)(string describe_message, TestPair pairs) { 619 foreach (pair; pairs) { 620 try { 621 if (_before_it) { 622 _before_it(); 623 } 624 625 pair.func(); 626 627 if (_after_it) { 628 _after_it(); 629 } 630 631 addSuccess(); 632 } catch (Throwable ex) { 633 addFail(describe_message, pair, ex); 634 } 635 } 636 } 637 638 /++ 639 The message should describe what the test should do. 640 641 Params: 642 message = The message to print when the test fails. 643 func = The delegate to call when running the test. 644 645 Examples: 646 ---- 647 int a = 4; 648 describe("example_library#a", 649 it("Should equal 4", delegate() { 650 a.shouldEqual(4); 651 }), 652 it("Should Not equal 5", delegate() { 653 a.shouldNotEqual(5); 654 }) 655 ); 656 ---- 657 +/ 658 TestPair it(string message, void delegate() func) { 659 TestPair retval; 660 retval.it_message = message; 661 retval.func = func; 662 663 return retval; 664 } 665 666 private struct TestPair { 667 string it_message; 668 void delegate() func; 669 } 670 671 private void addSuccess() { 672 _success_count++; 673 } 674 675 private void addFail(string describe_message, TestPair pair, Throwable err) { 676 import std..string : format; 677 678 _fail_messages[describe_message] ~= `"%s: %s" %s(%s)`.format(pair.it_message, err.msg, err.file, err.line); 679 _fail_count++; 680 } 681 682 private string[][string] _fail_messages; 683 private ulong _fail_count; 684 private ulong _success_count; 685 private void delegate() _before_it; 686 private void delegate() _after_it; 687 688 /* 689 TODO: 690 * Make indentation in docs use 4 space sized tabs 691 */