├── BankAccount ├── BankAccount.cpp ├── main.cpp ├── CMakeLists.txt ├── Tests.cpp ├── Requirements.md ├── Logger.h ├── BankAccount.h └── Logger.cpp ├── snakes_and_ladders ├── .gitignore ├── README.md ├── CMakeLists.txt ├── GAME_REQUIREMENTS.md ├── Snippets │ ├── verify_helper.cpp │ ├── roll_die.cpp │ ├── play.cpp │ ├── colour_printer.cpp │ └── action_type_printer.cpp └── main.cpp ├── left-pad ├── main.cpp ├── tests.cpp ├── CMakeLists.txt ├── README.md └── CATCH_CHEATSHEET.md ├── atdd-icon.png ├── .gitignore ├── README.md └── refs.md /BankAccount/BankAccount.cpp: -------------------------------------------------------------------------------- 1 | #include "BankAccount.h" 2 | -------------------------------------------------------------------------------- /snakes_and_ladders/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | cmake-build-* 3 | -------------------------------------------------------------------------------- /BankAccount/main.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | #include "catch.hpp" 3 | -------------------------------------------------------------------------------- /left-pad/main.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | #include "catch.hpp" 3 | 4 | -------------------------------------------------------------------------------- /atdd-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philsquared/a-tdd/HEAD/atdd-icon.png -------------------------------------------------------------------------------- /left-pad/tests.cpp: -------------------------------------------------------------------------------- 1 | #include "catch.hpp" 2 | 3 | 4 | 5 | TEST_CASE("left pad") { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /snakes_and_ladders/README.md: -------------------------------------------------------------------------------- 1 | # snakes_and_ladders 2 | Starting point for workshop on TDD using Catch2 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | BankAccount/.idea 2 | BankAccount/cmake-build-* 3 | left-pad/.idea 4 | **/cmake-build-* 5 | .idea 6 | -------------------------------------------------------------------------------- /left-pad/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | project(left_pad) 3 | 4 | set(CMAKE_CXX_STANDARD 11) 5 | 6 | add_executable(left_pad main.cpp tests.cpp) -------------------------------------------------------------------------------- /snakes_and_ladders/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(snakes_and_ladders) 3 | 4 | set(CMAKE_CXX_STANDARD 14) 5 | 6 | add_executable(snakes_and_ladders main.cpp) -------------------------------------------------------------------------------- /BankAccount/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | project(BankAccount) 3 | 4 | set(CMAKE_CXX_STANDARD 17) 5 | 6 | add_executable(BankAccount main.cpp BankAccount.cpp BankAccount.h Logger.cpp Logger.h Tests.cpp) -------------------------------------------------------------------------------- /snakes_and_ladders/GAME_REQUIREMENTS.md: -------------------------------------------------------------------------------- 1 | We need to be able to: 2 | * Have a game object 3 | * Add players 4 | * Each player has a colour (enum) 5 | * Players take turns rolling a die 6 | * Track moves and game state 7 | 8 | Start with a `take_turn()` method (see `play.cpp`). 9 | Also see `roll_die.cpp` for random number sample. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Accelerated TDD](atdd-icon.png) 2 | 3 | # Accelerated TDD - for more productive C++ 4 | 5 | This repository contains supplimentary material for my C++ TDD course, "Accelerated TDD". 6 | The course has (so far) been given as both one day and two day versions, 7 | and each time I give it we may cover some different material. 8 | 9 | * [References](refs.md) -------------------------------------------------------------------------------- /BankAccount/Tests.cpp: -------------------------------------------------------------------------------- 1 | #include "BankAccount.h" 2 | #include "catch.hpp" 3 | 4 | TEST_CASE() { 5 | 6 | Account bob( 123 ); 7 | Account alice( 456 ); 8 | 9 | bob.setBalance( 400 ); 10 | alice.setBalance( 500 ); 11 | 12 | transferFunds( bob, alice, 100.0 ); 13 | 14 | REQUIRE( bob.balance() == 300 ); 15 | REQUIRE( alice.balance() == 600 ); 16 | } 17 | -------------------------------------------------------------------------------- /snakes_and_ladders/Snippets/verify_helper.cpp: -------------------------------------------------------------------------------- 1 | // **** 2 | // **** Code snippets to save time - this file is not part of the project - do not add it! 3 | // **** 4 | 5 | 6 | // Helper lambda for verifying each turn in a progressing game 7 | 8 | 9 | auto verify = [&]( uint32_t dieRoll, Player::Colour col, Action action ) { 10 | face = dieRoll; 11 | auto turnInfo = game.take_turn(); 12 | CHECK( turnInfo.player.colour == col ); 13 | CHECK( turnInfo.action == action ); 14 | }; 15 | 16 | -------------------------------------------------------------------------------- /snakes_and_ladders/Snippets/roll_die.cpp: -------------------------------------------------------------------------------- 1 | // **** 2 | // **** Code snippets to save time - this file is not part of the project - do not add it! 3 | // **** 4 | 5 | // a not-great, but self-contained example implementation for modelling a random die roll 6 | // - will need an #include 7 | 8 | 9 | uint32_t roll_die() { 10 | static std::mt19937 rng; 11 | static bool initialised = false; 12 | if( !initialised ) { 13 | rng.seed( std::random_device()() ); 14 | initialised = true; 15 | } 16 | std::uniform_int_distribution<> rnd( 1, 6 ); 17 | return rnd(rng); 18 | } -------------------------------------------------------------------------------- /left-pad/README.md: -------------------------------------------------------------------------------- 1 | # Left Pad 2 | 3 | We've all heard of the notorious left-pad javascript library that was 4 | pulled from NPM a few years ago. 5 | We scoffed at how having a dependency on such a trivial library disrupted 6 | so much of the internet. 7 | But how trivial was it really? Let's implement it in C++! 8 | 9 | The requirements are: 10 | 11 | * Write a function that takes a string and a number and returns a string 12 | * The number represents the "padded" size. 13 | * The returned string should be the input string, 14 | padded with spaces on the left if shorter than the size argument. 15 | * The pad character should be optionally specifiable via an extra, defaulted argument 16 | 17 | -------------------------------------------------------------------------------- /snakes_and_ladders/Snippets/play.cpp: -------------------------------------------------------------------------------- 1 | // **** 2 | // **** Code snippets to save time - this file is not part of the project - do not add it! 3 | // **** 4 | 5 | 6 | // Demo game loop 7 | 8 | for( bool stillPlaying = true; stillPlaying;){ 9 | auto turn = game.take_turn(); 10 | 11 | std::cout << turn.player.colour << " "; 12 | switch( turn.action.type ) { 13 | case Action::Type::MoveTo: 14 | std::cout << "moves to " << turn.action.target << "\n"; 15 | break; 16 | case Action::Type::NoMove: 17 | std::cout << "misses go\n"; 18 | break; 19 | case Action::Type::Win: 20 | std::cout << "wins!\n"; 21 | stillPlaying = false; 22 | break; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /snakes_and_ladders/Snippets/colour_printer.cpp: -------------------------------------------------------------------------------- 1 | // **** 2 | // **** Code snippets to save time - this file is not part of the project - do not add it! 3 | // **** 4 | 5 | // helper for writing the Colour enum to a stream (let's Catch print) 6 | // - note this could also be done with a specialisation of Catch::StringMaker) 7 | 8 | std::ostream& operator <<( std::ostream& os, Colour colour ) { 9 | switch( colour ) { 10 | case Colour::Red: return os << "Red"; 11 | case Colour::Blue: return os << "Blue"; 12 | case Colour::Green: return os << "Green"; 13 | case Colour::Yellow: return os << "Yellow"; 14 | default: 15 | throw std::logic_error( "{unknown enum value: " + std::to_string( (int)colour ) + "}" ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /snakes_and_ladders/Snippets/action_type_printer.cpp: -------------------------------------------------------------------------------- 1 | // **** 2 | // **** Code snippets to save time - this file is not part of the project - do not add it! 3 | // **** 4 | 5 | // helper for writing Action and the Action::Type enum to a stream (let's Catch print) 6 | // - note this could also be done with a specialisation of, e.g., Catch::StringMaker) 7 | 8 | std::ostream& operator <<( std::ostream& os, Action::Type type ) { 9 | switch( type ) { 10 | case Action::Type::Move: return os << "Move"; 11 | case Action::Type::Win: return os << "Win"; 12 | default: 13 | throw std::logic_error( "{unknown enum value: " + std::to_string( (int)type ) + "}" ); 14 | } 15 | } 16 | 17 | std::ostream& operator <<( std::ostream& os, Action action ) { 18 | os << "Action{ " << action.type; 19 | if( action.type == Action::Type::MoveTo ) 20 | os << ", " << action.target; 21 | return os << " }"; 22 | } 23 | -------------------------------------------------------------------------------- /left-pad/CATCH_CHEATSHEET.md: -------------------------------------------------------------------------------- 1 | For more details you can see [the tutorial](https://github.com/catchorg/Catch2/blob/master/docs/tutorial.md) 2 | or [the reference docs](https://github.com/catchorg/Catch2/tree/master/docs). 3 | 4 | ``` 5 | #include "catch.hpp" 6 | 7 | TEST_CASE( "" ) { // To start a test case with name 8 | 9 | CHECK( add( 2, 3 ) == 6 ); // CHECK reports failure but continues 10 | REQUIRE( add( 2, 3 ) == 5 ); // REQUIRE reports and aborts on failure 11 | 12 | int i = 7; // Common value 13 | 14 | SECTION( "first section" ) { // Starts a section call "first section" 15 | CHECK( theAnswer() == 42 ); 16 | } 17 | 18 | SECTION( "second section" ) { // Starts another section 19 | // The whole test case will be executed twice: 20 | // - once for "first section", then again for "second section" 21 | // Only one of these sections will be entered each time 22 | INFO( "Only reported on failure" ); 23 | CAPTURE( i ); // reports value of i on failure 24 | 25 | FAIL( "This always fails" ); 26 | } 27 | } 28 | ``` -------------------------------------------------------------------------------- /BankAccount/Requirements.md: -------------------------------------------------------------------------------- 1 | You have an email from your boss. You need to make the following changes to this highly critical financial code: 2 | 3 | 1. The transfer function is screwing up when too much is transferred from an account - we need to check and throw an exception 4 | 2. If an exception is thrown either all funds must be transferred, or none (transactional) 5 | 3. People are complaining that BankAccount takes too long to build against. Remove iostreams from the header. 6 | 7 | The next day you get another email with some more work items - some more critical than others 8 | 9 | 4. They are still complaining - seems the logging header needs cleaning up. 10 | 5. There's a report that the tests are failing when logging at Debug level. Reproduce the issue, find and fix. 11 | 6. We have a requirement that whenever a balance changes it leaves an audit trail in the logs. The log must include the account number, the old balance and the new balance and must always be written. 12 | 7. Remove duplication in the logging macros. 13 | 14 | --- 15 | 16 | * You can use `REQUIRE_THROWS` and `REQUIRE_NOTHROW` in Catch to test for exceptions. 17 | 18 | * You can test for substrings using `REQUIRE_THAT( str, Contains( substr ) )` 19 | * (needs `using namespace Catch::Matchers` beforehand) -------------------------------------------------------------------------------- /snakes_and_ladders/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | struct Portal { 5 | enum class Type { Snake, Ladder }; 6 | Type type; 7 | uint32_t target; 8 | }; 9 | 10 | std::map const portals = 11 | { 12 | { 2, { Portal::Type::Ladder, 38 } }, 13 | { 4, { Portal::Type::Ladder, 14 } }, 14 | { 8, { Portal::Type::Ladder, 31 } }, 15 | { 21, { Portal::Type::Ladder, 42 } }, 16 | { 28, { Portal::Type::Ladder, 84 } }, 17 | { 36, { Portal::Type::Ladder, 44 } }, 18 | { 47, { Portal::Type::Snake, 26 } }, 19 | { 49, { Portal::Type::Snake, 11 } }, 20 | { 51, { Portal::Type::Ladder, 67 } }, 21 | { 56, { Portal::Type::Snake, 53 } }, 22 | { 62, { Portal::Type::Snake, 18 } }, 23 | { 64, { Portal::Type::Snake, 60 } }, 24 | { 71, { Portal::Type::Ladder, 91 } }, 25 | { 80, { Portal::Type::Ladder, 100 } }, 26 | { 87, { Portal::Type::Snake, 24 } }, 27 | { 93, { Portal::Type::Snake, 73 } }, 28 | { 95, { Portal::Type::Snake, 75 } }, 29 | { 98, { Portal::Type::Snake, 78 } } 30 | }; 31 | -------------------------------------------------------------------------------- /BankAccount/Logger.h: -------------------------------------------------------------------------------- 1 | #ifndef LOGGER_H 2 | #define LOGGER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | enum class LogLevel { 11 | Error, 12 | Warning, 13 | Info, 14 | Debug 15 | }; 16 | 17 | class Logger { 18 | static LogLevel s_currentLogLevel; 19 | 20 | public: 21 | virtual ~Logger(); 22 | 23 | virtual void log( LogLevel level, std::string_view line ) = 0; 24 | 25 | static void setLevel( LogLevel newLevel ); 26 | static LogLevel level() { return s_currentLogLevel; } 27 | }; 28 | 29 | Logger& logger(); 30 | 31 | #define LOG_ERROR( text ) do { if( Logger::level() >= LogLevel::Error ){ std::ostringstream oss; oss << text; logger().log( LogLevel::Error, oss.str() ); } } while( false ) 32 | #define LOG_WARN( text ) do { if( Logger::level() >= LogLevel::Warning ){ std::ostringstream oss; oss << text; logger().log( LogLevel::Warning, oss.str() ); } } while( false ) 33 | #define LOG_INFO( text ) do { if( Logger::level() >= LogLevel::Info ){ std::ostringstream oss; oss << text; logger().log( LogLevel::Info, oss.str() ); } } while( false ) 34 | #define LOG_DEBUG( text ) do { if( Logger::level() >= LogLevel::Debug ){ std::ostringstream oss; oss << text; logger().log( LogLevel::Debug, oss.str() ); } } while( false ) 35 | 36 | 37 | 38 | #endif // LOGGER_H 39 | -------------------------------------------------------------------------------- /BankAccount/BankAccount.h: -------------------------------------------------------------------------------- 1 | #ifndef BANKACCOUNT_H 2 | #define BANKACCOUNT_H 3 | 4 | #include 5 | #include "Logger.h" 6 | 7 | class Account { 8 | double m_balance = 0; 9 | int m_accountNumber; 10 | public: 11 | Account( int accountNumber ) : m_accountNumber( accountNumber ) {} 12 | 13 | auto accountNumber() const { return m_accountNumber; } 14 | auto balance() const { return m_balance; } 15 | void setBalance( double amount ) { 16 | LOG_INFO( "setting balance to: " << amount << " in Acc# " << m_accountNumber ); 17 | m_balance = amount; 18 | } 19 | 20 | friend void transferFunds( Account& fromAccount, Account& toAccount, double amount ) { 21 | try { 22 | LOG_INFO("Transferring " << amount << " from Acc# " << fromAccount << " to Acc# " << toAccount); 23 | toAccount.m_balance += amount; 24 | LOG_DEBUG("Removed: " << amount << " from Acc# " << fromAccount << " - about to transfer to Acc# " 25 | << toAccount); 26 | fromAccount.m_balance -= amount; 27 | } 28 | catch(...) { 29 | // This should never happen 30 | } 31 | } 32 | 33 | friend auto operator<< ( std::ostream& os, Account const& account ) -> std::ostream& { 34 | return os << account.accountNumber(); 35 | } 36 | }; 37 | 38 | #endif // BANKACCOUNT_H 39 | -------------------------------------------------------------------------------- /BankAccount/Logger.cpp: -------------------------------------------------------------------------------- 1 | #include "Logger.h" 2 | 3 | #include 4 | 5 | std::string logFile = "log.txt"; 6 | 7 | LogLevel Logger::s_currentLogLevel = LogLevel::Info; 8 | 9 | void Logger::setLevel( LogLevel newLevel ) { 10 | s_currentLogLevel = newLevel; 11 | } 12 | 13 | Logger::~Logger() {} 14 | 15 | 16 | class FileLogger : public Logger { 17 | std::string m_filename; 18 | bool m_firstLog = true; 19 | 20 | public: 21 | FileLogger( std::string filename ) : m_filename( std::move( filename ) ) {} 22 | 23 | 24 | void log( LogLevel level, std::string_view line ) override { 25 | std::ofstream of; 26 | if( m_firstLog ) { 27 | of.open( m_filename ); 28 | m_firstLog = false; 29 | } 30 | else { 31 | of.open( m_filename, std::ios::app ); 32 | } 33 | 34 | if( line.size() > 55 ) 35 | throw std::domain_error("Log line too long"); 36 | 37 | // Get current date-time in printable form 38 | std::time_t now = std::time(0); 39 | std::tm tstruct = *std::localtime(&now); 40 | char buf[80]; 41 | std::strftime(buf, sizeof(buf), "%Y-%m-%d %X", &tstruct); 42 | of << buf << " "; 43 | 44 | switch( level ) { 45 | case LogLevel::Error: of << " ERROR"; break; 46 | case LogLevel::Warning: of << "WARNING"; break; 47 | case LogLevel::Info: of << " INFO"; break; 48 | case LogLevel::Debug: of << " DEBUG"; break; 49 | } 50 | of << " | " << line << std::endl; 51 | } 52 | 53 | }; 54 | 55 | Logger& logger() { 56 | static std::unique_ptr s_logger = std::make_unique(logFile ); 57 | return *s_logger; 58 | } 59 | -------------------------------------------------------------------------------- /refs.md: -------------------------------------------------------------------------------- 1 | # References 2 | 3 | The following are some of the references, and source materials, from my class, Accelerated TDD 4 | 5 | ## Slides: 6 | * [Day 1](https://www.dropbox.com/scl/fi/rawu61d6937ucxr6a5l93/AccTDD-1.pdf?rlkey=tcymlztqhdkjlzuwnuv26gd47&dl=0) 7 | * [Day 2](https://www.dropbox.com/s/1c3lv1v9usezhq1/AccTDD-2.pdf?dl=0) 8 | 9 | ## Talks: 10 | 11 | * [Kevlin Henney - Seven Ineffective Coding Habits (timed to section on TDD)](https://www.youtube.com/watch?v=SUIUZ09mnwM&feature=youtu.be&t=49m52s) 12 | * [Kevlin Henney - Deconstructing SOLID](https://vimeo.com/157708450) 13 | * [Dan North on Deliberate Learning](https://www.youtube.com/watch?v=SPj-23z-hQA) 14 | * [Dan North - Why Every Single Element of SOLID is Wrong! (slides)](https://speakerdeck.com/tastapod/why-every-element-of-solid-is-wrong) 15 | * [Klaus Iglberger - Breaking Dependencies: The SOLID Principles](https://www.youtube.com/watch?v=Ntraj80qN2k) 16 | * [Marshall Clow - Making Your Library More Reliable with Fuzzing](https://www.youtube.com/watch?v=LlLJRHToyUk) 17 | * [Patryk Małek - Property Based Testing (with RapidCheck)](https://www.youtube.com/watch?v=aiapg-3vDcQ) 18 | * [Clare Macrae on Approval Testing & Legacy code](https://www.youtube.com/watch?v=tXEuf_3VzRE) 19 | * [Kevlin Henney - Introducing the FLUID principles (Slides)](https://www.slideshare.net/Kevlin/introducing-the-fluid-principles) 20 | * [Seph De Busser - Testing The Tests: Mutation Testing for C++](https://www.youtube.com/watch?v=M-5_M8qZXaE) 21 | * [Viktor Kirilov - The Hitchhiker's Guide to Faster Builds](https://www.youtube.com/watch?v=anbOy47fBYI) 22 | * [Steve Freeman - Fractal TDD](https://www.youtube.com/watch?v=7D3t8m4ejMk) 23 | * [Phil Nash on Catch (1.x)](https://www.youtube.com/watch?v=C2LcIp56i-8) 24 | * [Phil Nash on Catch2](https://www.youtube.com/watch?v=3tIE6X5FjDE) 25 | * [Phil Nash - SOLID Revisited](https://www.youtube.com/watch?v=Ko0eV7BGcXs) 26 | * [Phil Nash - Seeking Simplicity](https://www.youtube.com/watch?v=EeviEFkKb6M) 27 | * [Phil Nash - Zen and the Art of Code Lifecycle Maintenance](https://www.youtube.com/watch?v=_vl8hj7NUXs) 28 | * [Phil Nash - Rewiring your Brain with Test Driven Thinking](https://www.youtube.com/watch?v=Hx-1Wtvhvgw) 29 | 30 | ## Books: 31 | 32 | * [Working Effectively With Legacy Code - Michael Feathers](https://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052/ref=sr_1_1?ie=UTF8&qid=1538268311&sr=8-1&keywords=working+with+legacy+code) 33 | * [Growing Object-Oriented Software Guided by Tests](http://www.growing-object-oriented-software.com) 34 | * [Clean Code - Robert Martin](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882/ref=sr_1_2?ie=UTF8&qid=1538268311&sr=8-2&keywords=working+with+legacy+code) 35 | * [Refactoring - Martin Fowler](https://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672/ref=sr_1_4?ie=UTF8&qid=1538268311&sr=8-4&keywords=working+with+legacy+code) 36 | * [Domain Driven Design - Eric Evans](http://dddcommunity.org/book/evans_2003/) 37 | 38 | ## Articles & Blogs 39 | * [Phil Nash - More Productive C++ with TDD](https://levelofindirection.com/blog/more-productive-cpp-with-tdd.html) 40 | * [Michael Feathers' A Set of Unit Testing Rules](https://www.artima.com/weblogs/viewpost.jsp?thread=126923) 41 | * [Michael Feathers' The Flawed Theory Behind Unit Testing](http://michaelfeathers.typepad.com/michael_feathers_blog/2008/06/the-flawed-theo.html) 42 | * [Dan North - Introducing BDD](https://dannorth.net/introducing-bdd/) 43 | * [Nat Pryce on using Property Based Testing with TDD](http://www.natpryce.com/articles/000795.html) 44 | * [Robert Martin - The Transformation Priority Premise](https://8thlight.com/blog/uncle-bob/2013/05/27/TheTransformationPriorityPremise.html) 45 | * [Computerworld article reporting on NIST report on software bugs](http://www.computerworld.com/article/2575560/it-management/study--buggy-software-costs-users--vendors-nearly--60b-annually.html) 46 | * [Critiques of NIST report on software bugs](http://www.satisfice.com/nist.htm) 47 | * [PRWeb Article on 2013 Cambridge University study on cost of software bugs](http://www.prweb.com/releases/2013/1/prweb10298185.htm) 48 | * [Scott Wlaschin on The Roman Numerals Kata](https://fsharpforfunandprofit.com/posts/roman-numeral-kata/) 49 | * [The Mikado Method](https://livebook.manning.com/book/the-mikado-method/chapter-1) 50 | 51 | ## Libraries & Frameworks 52 | * [Catch2 (Phil Nash's test framework)](http://catch-lib.net) 53 | * [C++ Approval Tests (Clare Macrae's work with Llewellyn Falco)](https://github.com/approvals/ApprovalTests.cpp) 54 | * [Hippomocks (Peter Bindels' Mocking framework)](http://www.hippomocks.com) 55 | * [Trompeloeil (Björn Fahller's Mocking framework)](https://github.com/rollbear/trompeloeil) 56 | * [Rapidcheck (Property-Based testing)](https://github.com/emil-e/rapidcheck) 57 | 58 | ## Katas: 59 | 60 | * [Jeff Attwood on coding katas](https://blog.codinghorror.com/the-ultimate-code-kata/) 61 | * [Emily Bache's repo of coding katas](https://github.com/emilybache/start-points-custom) 62 | 63 | ## Other resources: 64 | 65 | * [Cyber-dojo](http://cyber-dojo.org) 66 | * [Provable Refactorings (repo)](https://github.com/digdeeproots/provable-refactorings) 67 | 68 | --------------------------------------------------------------------------------