Skip to content

GradedJestRisk/refactoring-test

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Table of contents

Origin & inspirations

This code* has been ported from a copyrighted C# version, included in the Unit Testing Principles, Practices, and Patterns book, published by Manning. The author, Vladimir Khorikov, has explicitly allowed such use here. Otherwise stated, all information and quotes in this file comes from the book. I'm not linked in any way with the author; I would like to experiment its proposals and share them with you.

*: except PostgreSQL PL/SQL version, that I wrote by myself. Referred to as pg-pl-sql in codebase, it'a procedural (imperative) language, executed by the database. Basically, it allows mixing "functional" SQL statements together using basic imperative structures (control flow, variables, array). The aim of using such an unusual programming language is to show cost/benefits of such a compact implementation.

Regarding hexagonal architecture, there is a plenty of folders naming convention (see the update note in this blog post):

  • domain: port / adapter
  • user-side / application
  • server-side / infrastructure

Characterization testing complies with to Michael Feathers definition

Scope

Only entreprise applications are in the scope.

An entreprise application is an application that aims at automating or assisting an organization's inner processes. It can take many forms, but usually the characteristics of an entreprise software are:

  • high business logic complexity;
  • long project lifespan;
  • moderate amounts of data;
  • low or moderate performance requirements.

Only back-office API is in the scope (GUI are off-scope).

Book's these overview

Hypothesis

Automated testing aims at making change cheaper, by making sure:

  • the new behaviour is what you expected
  • other existing behaviours have not been affected

Automated testing is implemented by code, which should be payed for several times:

  • implementation time;
  • maintenance time.

Therefore, test can make change costlier.

Several ways to design tests:

  • mockist school
    • mock all dependencies (eg. most collaborators)
    • this cause test code bloat and test brittleness: such test raises false positive while refactoring
  • classical school
    • mock only shared dependencies (eg. DB)
    • this cause less, but still some test brittleness
  • output-based test
    • mock only unmanaged shared dependencies (eg. external API)
    • brittleness is minimum
  • output-based test can be achieved
    • by restricting side effect to dedicated areas in production code
    • such containment is provided by indirection, implemented by application architecture pattern: hexagonal, functional

Output-based test look like the best bet.

Transitioning a codebase to output-based testing is a 2 steps refactoring process:

  • refactor production code (thus breaking existing brittle test) to achieve containment
  • refactor test code

Conclusion

Coding is a tradeoff between code performance and change cost

Indirection Code change Code performance Test change
None costlier higher costlier
Some cheaper lower cheaper
Too many costlier lower costlier

Indirection refers to can be implemented by design patterns, layered architecture.

Code change (maintenance) cost can be broken down in time:

  • to understand existing code
  • to make appropriate change
  • fix unintended side effects (regression)

Test change (maintenance) cost can be broken down in time:

  • to understand existing test
  • to make appropriate change to support a code feature change
  • to make appropriate change to support a code refactoring change (false positive)

1: Indirection samples are design patterns, layered architecture

Target testing strategy

Test types:

  • unit test
    • scope :
      • domain only
      • all paths
    • isolation :
      • use real collaborators
      • no mock
  • integration test:
    • scope :
      • happy path, exercice all components in the least possible scenarios
      • all other paths untested in unit test
    • isolation:
      • use real collaborators in
        • application, domain
        • infrastructure : managed dependencies only (eg. DB)
      • mock
        • infrastructure: unmanaged dependency only (here, external message bus/API)
        • using handwritten mock

Implementation

Codebase comes in several flavors, to widen understanding:

  • procedural (no OOP)
    • pg-pl-sql
    • Javascript
  • object-oriented
    • hexagonal architecture

Repository building

It will follow these steps:

Install

Pre-requisites

You'll need:

  • node and npm (I used nvm)
  • a running postgresql instance:

Steps

  • get the source:
    • with git: clone the repo : git clone git@github.com:GradedJestRisk/refactoring-test.git && cd refactoring-test
    • without git:
      • or download the source code manually, extract and cd
      curl -LJO https://github.com/GradedJestRisk/refactoring-test/archive/master.zip
      unzip refactoring-test-master.zip
      cd    refactoring-test-master
      
  • install dependencies npm install
  • setup your database connection in knexfile.js
    connection: {
      database: '<DATABASE_NAME>',
      port:     <PORT>,
      user:     '<USER',
      password: '<PASSWORD>' 
    },
  • create DB structure: run npm run db:create-schema
  • run the test npm test

Development purpose

To interactively make API calls on OOP Hexagonal JS:

  • create sample data with npm run db:insert-data
  • start API with npm start
  • check it's up
    • make a health check call curl --request GET 'http://localhost:3000/health_check'
    • check the response {"name":"refactoring-test","version":"1.0.0","description":"javascript port of https://www.manning.com/books/unit-testing C# refactor kata"}%
    • check the log 127.0.0.1: GET /health_check --> 200
  • make an actual call
    • get user: curl --request GET 'http://localhost:3000/users/0'
    • change its email:
      curl --header "Content-Type: application/json" \ --request POST \ --data '{"id":"0","email":"foo@bar.com"}' \ localhost:3000/users/0/email

To run characterization tests interactively in your IDE on an implementation:

} else {
    // used for interactive
    sutPath = sutPathProceduralJS;
}

To see all SQL queries issued by JS:

  • enable debug mode by uncommenting the following line in knexfile.js
  // debug: true

Compare implementations

An overview

Units:

  • time: milliseconds
  • size: characters
Implementation Code
size
Code
execution time
Unit test
size
Unit test
execution time
Integration test
size
Integration test
execution time
Char. test
execution time
End-to-end test
size
End-to-end test
execution time
Procedural DB 4 250 1 (3 from node) N/A N/A N/A N/A 1 000 N/A N/A
Procedural JS 2 750 220 N/A N/A N/A N/A 2 000 N/A N/A
OOP Hexagonal JS 5 590 220 2 500 370 4 258 570 2 000 3 496 2 101
Helper Code
Char test 9 000
Char test helper 1 000

Details

Run npm run benchmark To get procedural pl/sql execution time out of node, execute function SELECT get_execution_time_micro();

Releases

No releases published

Packages

No packages published