|
109 | 109 |
|
110 | 110 | ---- |
111 | 111 |
|
112 | | -##Costs associated with First-Class Tests |
| 112 | +##Cost per defect and First-Class Tests |
113 | 113 |
|
114 | 114 |  |
115 | 115 |
|
|
367 | 367 | ```cpp |
368 | 368 | "should read 42"_test = [] { |
369 | 369 | MockReader reader{}; |
370 | | - // At least we can use the same front-end |
371 | 370 |
|
| 371 | + // At least we can use the same front-end |
372 | 372 | EXPECT_CALL(reader, read).WillOnce(Return(42)); |
373 | 373 |
|
374 | 374 | read(reader); |
|
520 | 520 | } {} |
521 | 521 |
|
522 | 522 | template <class TName, class R, class... TArgs> |
523 | | - auto call(TArgs&&... args) { |
| 523 | + auto call_(TArgs&&... args) { |
524 | 524 | return poly_.virtual_(TName{})(poly_, args...); |
525 | 525 | } |
526 | 526 |
|
|
653 | 653 | }; |
654 | 654 | ``` |
655 | 655 |
|
656 | | -#### We can only do Integration-tests or black box tests here! |
| 656 | +#### We can only do Integration-testing or black box testing here! |
657 | 657 |
|
658 | 658 | ---- |
659 | 659 |
|
|
690 | 690 | }; }; |
691 | 691 | ``` |
692 | 692 |
|
| 693 | +```cpp |
| 694 | +class Reader final class Printer final |
| 695 | + : public IReader { : public IPrinter { |
| 696 | +public: public: |
| 697 | + int read() override; void print(int) override; |
| 698 | +}; }; |
| 699 | +``` |
| 700 | + |
693 | 701 | ---- |
694 | 702 |
|
695 | 703 | ### <strike>Stupid</strike> vs SOLID |
|
720 | 728 |
|
721 | 729 | ```cpp |
722 | 730 | "should print read value"_test = [] { |
723 | | - NiceGMock<ILogger> logger{}; // ignore an uninteresting call |
724 | | - StrictGMock<IReader> reader{}; // fail on uninteresting call |
725 | | - StrictGMock<IPrinter> printer{}; // fail on uninteresting call |
726 | | - App app{reader, printer, logger}; // wiring! |
| 731 | + // Some boilerplate introduced! |
| 732 | + NiceGMock<ILogger> logger{}; // Ignore an uninteresting call |
| 733 | + StrictGMock<IReader> reader{}; // Fail on uninteresting call |
| 734 | + StrictGMock<IPrinter> printer{}; // Fail on uninteresting call |
| 735 | + App app{reader, printer, logger}; // Wiring! |
727 | 736 |
|
728 | 737 | EXPECT_CALL(reader, read).WillOnce(Return(42)); |
729 | 738 | EXPECT_CALL(printer, print, 42); |
|
790 | 799 |
|
791 | 800 | ```cpp |
792 | 801 | "should print read value"_test = [] { |
793 | | - NiceGMock<Loggable> logger{}; // ignore an uninteresting call |
794 | | - StrictGMock<Readable> reader{}; // fail on uninteresting call |
795 | | - StrictGMock<Printable> printer{}; // fail on uninteresting call |
| 802 | + NiceGMock<Loggable> logger{}; // Ignore an uninteresting call |
| 803 | + StrictGMock<Readable> reader{}; // Fail on uninteresting call |
| 804 | + StrictGMock<Printable> printer{}; // Fail on uninteresting call |
796 | 805 | App app{reader, printer, logger}; // C++17 template argument |
797 | 806 | // deduction for class templates |
798 | 807 |
|
|
806 | 815 |
|
807 | 816 | ---- |
808 | 817 |
|
809 | | -### App - Type-Erasure - Virtual Concepts |
| 818 | +### App - Type-Erasure - Virtual Concepts (Dynamic Dispatch without inheritance) |
810 | 819 |
|
811 | 820 | ```cpp |
812 | 821 | class App { |
|
831 | 840 | ### App - Unit-Testing - Type-Erasure |
832 | 841 | ```cpp |
833 | 842 | "should print read value"_test = [] { |
834 | | - NiceGMock<Loggable> logger{}; // ignore an uninteresting call |
835 | | - StrictGMock<Readable> reader{}; // fail on uninteresting call |
836 | | - StrictGMock<Printable> printer{}; // fail on uninteresting call |
| 843 | + NiceGMock<Loggable> logger{}; // Ignore an uninteresting call |
| 844 | + StrictGMock<Readable> reader{}; // Fail on uninteresting call |
| 845 | + StrictGMock<Printable> printer{}; // Fail on uninteresting call |
837 | 846 | App app{reader, printer, logger}; // C++17 template argument |
838 | 847 | // deduction for class templates |
839 | 848 |
|
|
847 | 856 |
|
848 | 857 | ---- |
849 | 858 |
|
850 | | -### Are there any problems with `SOLID` approach? |
| 859 | +### So far so good, but are there any problems with `SOLID` approach? |
851 | 860 |
|
852 | 861 | ---- |
853 | 862 |
|
854 | | -### Manual Dependency Injection - Wiring Mess |
| 863 | +### Wiring Mess |
855 | 864 |
|
856 | 865 | > `SOLID` allows testable design but it moves the problem to the caller! |
857 | 866 |
|
|
1168 | 1177 | ###Automatic mocks injection - How? |
1169 | 1178 |
|
1170 | 1179 | ```cpp |
| 1180 | +template<class TArgs, class TMocks> |
1171 | 1181 | struct mocker { |
1172 | | - mocks_t& mocks; |
1173 | | - args_t& args; |
| 1182 | + const TArgs& args; |
| 1183 | + TMocks& mocks; |
1174 | 1184 |
|
1175 | 1185 | template<class T, REQUIRES(std::is_polymorphic<raw_t<T>>{})> |
1176 | 1186 | operator T() const { |
|
1200 | 1210 |
|
1201 | 1211 | ---- |
1202 | 1212 |
|
1203 | | -### Okay, that's fancy, but how testable code might be produced by default? |
| 1213 | +### Okay, that's fancy, but how it can be used to produce a testable code by default? |
1204 | 1214 |
|
1205 | 1215 | ### Solution -> Test Driven Development |
1206 | 1216 |
|
|
1472 | 1482 | ### BDD/RED - Write a bit of test |
1473 | 1483 |
|
1474 | 1484 | ```cpp |
1475 | | -"[0.0.1] Parse feeds [empty stream of feeds]"_test = [] { |
| 1485 | +"[0.0.1] Parse feeds [should connect on start]"_test = [] { |
1476 | 1486 | GIVEN("A trading system", |
1477 | 1487 | auto [ts, mocks] = testing::make<trading_system>() |
1478 | 1488 | ); |
|
1485 | 1495 |
|
1486 | 1496 | ---- |
1487 | 1497 |
|
1488 | | -### BDD/GREEN - Make it pass |
| 1498 | +### BDD/GREEN - Make it compile/pass (The simplest way) |
1489 | 1499 |
|
1490 | 1500 | ```cpp |
1491 | 1501 | template<class TMarketData = Streamable(class MarketData)> |
|
1523 | 1533 |
|
1524 | 1534 | ---- |
1525 | 1535 |
|
1526 | | -### BDD/GREEN - Make it pass |
| 1536 | +### BDD/GREEN - Make it pass again... |
1527 | 1537 |
|
1528 | 1538 | ```cpp |
1529 | 1539 | template<...> |
1530 | 1540 | class trading_system { |
1531 | 1541 | public: |
1532 | 1542 | ... |
1533 | 1543 | void process() { |
1534 | | - auto buffer = marketData.read(); |
1535 | | - if (buffer.empty()) { |
| 1544 | + if (auto buffer = marketData.read(); buffer.empty()) { |
1536 | 1545 | marketData.disconnect(); |
1537 | 1546 | } |
1538 | 1547 | } |
|
1545 | 1554 |
|
1546 | 1555 | ---- |
1547 | 1556 |
|
| 1557 | +#### 1. TDD/RED - Write a bit of test (expectations/intentions) |
| 1558 | +
|
| 1559 | +```cpp |
| 1560 | +"should connect to the stream on creation"_test = [] { |
| 1561 | + auto [fh, mocks] = testing::make<feed_handler()>(mocks); |
| 1562 | + EXPECT_CALL(mocks<Streamable>(), connect); |
| 1563 | + fh(); // create feed_handler |
| 1564 | +}; |
| 1565 | +``` |
| 1566 | +
|
| 1567 | +---- |
| 1568 | +
|
| 1569 | +#### 2. TDD/GREEN - Make it compile/pass (the simpler way) |
| 1570 | +
|
| 1571 | +```cpp |
| 1572 | +template<class TStream = Streamable> |
| 1573 | +class feed_handler { |
| 1574 | +public: |
| 1575 | + explicit feed_handler(TStream& stream) { |
| 1576 | + stream.connect(); |
| 1577 | + } |
| 1578 | +}; |
| 1579 | +``` |
| 1580 | +
|
| 1581 | +---- |
| 1582 | +
|
| 1583 | +## TDD and eXtreme programming / Pairing |
| 1584 | +
|
| 1585 | +<img src="images/pair.png" width="85%" /> |
| 1586 | +
|
| 1587 | +> 1. One dev is writing a test and the other is making it pass (the simplest way) |
| 1588 | +> 2. Switch the roles! |
| 1589 | +
|
| 1590 | +---- |
| 1591 | +
|
| 1592 | +#### 1. TDD/RED - Write another test |
| 1593 | +
|
| 1594 | +```cpp |
| 1595 | +"should disconnect when read an empty stream"_test = [] { |
| 1596 | + auto [fh, mocks] = testing::make<feed_handler()>(); |
| 1597 | + |
| 1598 | + InSequence sequence; |
| 1599 | + EXPECT_CALL(mocks<Streamable>(), connect()); |
| 1600 | + EXPECT_CALL(mocks<Streamable>(), read()).WillOnce(Return("")); |
| 1601 | + EXPECT_CALL(mocks<Streamable>(), disconnect()); |
| 1602 | + |
| 1603 | + EXPECT(not fh().process()); |
| 1604 | +}; |
| 1605 | +``` |
| 1606 | +
|
| 1607 | +---- |
| 1608 | +
|
| 1609 | +#### 2. TDD/GREEN - Make all tests pass |
| 1610 | +
|
| 1611 | +```cpp |
| 1612 | +template<class TStream = Streamable> |
| 1613 | +class feed_handler { |
| 1614 | +public: |
| 1615 | + explicit feed_handler(TStream& stream) { |
| 1616 | + stream.connect(); |
| 1617 | + } |
| 1618 | + |
| 1619 | + auto process() { |
| 1620 | + if (auto buffer = stream.read(); buffer.empty()) { |
| 1621 | + stream.disconnect(); |
| 1622 | + return false; |
| 1623 | + } |
| 1624 | + return true; |
| 1625 | + } |
| 1626 | + |
| 1627 | +private: |
| 1628 | + TStream& stream; |
| 1629 | +}; |
| 1630 | +``` |
| 1631 | +
|
1548 | 1632 | ---- |
1549 | 1633 |
|
1550 | 1634 | ### Commit |
|
1580 | 1664 |
|
1581 | 1665 | ---- |
1582 | 1666 |
|
1583 | | -## TDD and eXtreme programming / Pairing |
1584 | | -
|
1585 | | -<img src="images/pair.png" width="85%" /> |
1586 | | -
|
1587 | | -> 1. One dev is writing a test and the other is making it pass (the simplest way) |
1588 | | -> 2. Switch the roles! |
1589 | | -
|
1590 | | ----- |
1591 | | -
|
1592 | 1667 | <img style="background:none; border:none; box-shadow:none;" src="images/done.png" /> |
1593 | 1668 |
|
1594 | 1669 | * #### Take the next story... |
|
0 commit comments