Unit Testing
Unit testing
Definitions and Best Practices
The Ideal testing pyramid
Source: martinfowler.com
Our testing pyramid
Source: agile faqs
Unit Test: a definition
A unit test is an automated piece of code that invokes a unit of work in the system and
then checks a single assumption about the behavior of that unit of work.
A unit of work is a single logical functional use case in the system that can be invoked by
some public interface (in most cases). A unit of work can span a single method, a whole class
or multiple classes working together to achieve one single logical purpose that can be
verified.
Source: The art of unit testing
How is it different that an integration test?
An integration test usually is not in-memory: it may touch the disk file system, databases,
registry or other shared resources. It may read or need a configuration file of the file system,
whereas a unit test can be fully configured in-memory, for example.
An integration test will usually run much slower because it does not run in-memory.
An integration test can be less consistent – it may use threads or random number generators
so that the result of the test is not always consistent.
Source: Roy Osherove Blog
● Self-descriptive
● No conditional logic, No loops
● No exception handling
● Assertions & Assertion messages
● No test logic in production code
● Test code organization & structure
Unit test: best practices
● 3 Steps
● Fast
● Consistent
● Atomic & Isolated
● Single responsibility
● Environment isolation
● Classes isolation
● Fully automated
3 steps - AAA
● Setup
● Prepare an input
● Call a method
● Check an output
● Tear down
Fast
Frequent execution
● Several times per day [Test-after development]
● Several times per hour [Test driven development]
● Every few minutes [IDE - Execute after save]
Execution in groups
● 100 tests = Execution time x 100
● Weak link: a slow test slows the whole suite
Multiple invocations of the test should consistently return true or consistently return false, provided
no changes was made on code.
So we cannot:
DateTime currentDateTime = DateTime.Now;
int currentValue = new Random().Next();
We can use Mocks and Dependency Injection (DI)
Consistent
Atomic & Isolated
● Only two possible results: Pass or Fail
● No partial results
● Different execution order must yield same results.
● Test B should not depend on outcome of Test A
● Use Mocks instead of Dependencies
Single Responsibility
One test should be responsible for one scenario
So we test behavior not methods
● 1 methdod, n behaviors → n tests
● n methods, 1 behavior → 1 test
Environment Isolation
Unit tests should be isolated from an environmental influences
● Database
● Web services
● Environment variables
● Property files
● System date/time
Environment Isolation
In order to achieve Environment Isolation we use Mocks and Fakes
● Handwritten mock / fake objects
● Isolation Frameworks (RhinoMocks, NSubstitute etc)
Goals
● Minimal method calls in a test
● Minimal tests per method
● High code coverage
Anti-patterns
● Constrained test order
● Hidden test call
● Shared state corruption (bad set up or teardown, use of singletons or static instances)
Classes Isolation
Fully automated
Automated execution
Automated decision making (success or failure)
IDE Integration
Self-descriptive
A Unit test
● Is development level documentation
● Ensures that method specs are up to date
● Naming template: Method_Scenario_ExpectedBehavior() or Method_State_ExpectedBehavior()
● Test only one thing
● Meaningful assert message
● Separating Assert from Actions (Wrong: Assert.AreEqual(COULD_NOT_READ_FILE,log.GetLineCount("abc.txt"));)
No conditional logic, No loops
No conditional logic
● Conditional logic reveals different behavior
● All inputs should be known
● Expected output should be strictly defined
● Instead of “if” or “switch” we split into two or more tests
No loops
● If we need to repeat the test logic then it's too complicated
No Exception Handling
● Catch only the expected type of exception
● Fail test if expected exception is not caught
● Let all other exceptions uncaught
Assertions & Assertion messages
● Extend Assert with custom assertions (object comparer, repetitive conditions etc)
● Expressive message (reading the message only should be able to recognize the problem)
● Include business logic info (input values etc.)
● Consider assert messages as code documentation
No test logic in production code
Separate projects for production code and tests
Do not create methods/fields/properties only for testing
Dependency Injection
Test code organization & structure
● Source control
● File structure
○ One test class per feature or
○ One test class per class under test
● Test code structure
○ Abstractions
○ Inheritance
○ Template class
○ Generics
Unit Testing
Design prerequisites, pros & cons
Design for testability
● Make methods virtual
● Interface-based design
● Non Sealed classes
● Do not instantiate classes inside methods
● Do not call static methods directly
● Do not use static constructors
● Do not use construction logic
● Separate singletons and singleton holders
Pros
● Clean code
● Safer refactoring
● Decoupled code
● Documentation of requirements
● Cheaper maintenance
Cons
● Amount of work
● Complexity
● Exposing sensitive info
● Sometimes you can’t
Pros & cons
● The Liar (test passes but not really testing)
● Excessive Setup (might be integration test?)
● The Giant (alt to God Class)
● The Mockery (similar to Excessive Setup)
● The Inspector
● Generous Leftovers
● The Local Hero (dev environment dependencies)
● The Nitpicker (asseting unimportant details)
● The Secret Catcher
Unit Testing Anti-patterns
● The Sequencer (order dependency)
● Hidden Dependency
● The Enumerator (bad naming)
● The Stranger (not relative to the case)
● The Distant Relative
● The Operating System Evangelist
● Success Against All Odds
● The Free Ride (stuffing assertions)
● The One (Giant & Free Ride)
Unit Testing
Working with legacy code
Legacy code
Definition: Legacy code is the code without tests
The Legacy Code Dilemma:
When we change code, we should have tests in place. To put tests in place, we often
have to change code.
The Legacy Code Change Algorithm
1. Identify change points
2. Find test points
3. Break dependencies
4. Write tests
5. Make changes and refactor
Legacy code
Where to start adding tests?
Rate every component for
● Logical complexity
● Dependency level
● Priority
Legacy code
Easy-first approach Hard-first approach
Unit Testing
Real world examples
Isolation frameworks
Unit Testing Frameworks
Real world example: Unit
Real world example: Unit Test
Real world example: Unit Test
Here we can discuss about the need of this unit test and if the “unit” should follow the creative
or defensive programming strategy and trust the input or not!
Real world example: Unit
Dependencies
Before we test we must obviously isolate the unit from CookieHelper and TimeZoneInfo dependencies.
We can also safely refactor the unit by breaking it into two pieces.
A simple safe refactoring: extract method with Resharper
A simple safe refactoring: extract method with Resharper
Refactored methods
Breaking the dependencies with Dependency Injection
1) Use the Bridge design pattern to separate a class's interface from its
implementation so you can vary or replace the implementation without
changing the client code
Breaking the dependencies with Dependency Injection
Bridging the CookieHelper and TimeZoneInfo classes
Breaking the dependencies with Dependency Injection
2) Create a non-static DateExtensions class and Inject Dependencies
Finally: The Test
Scenario: date: 2/2/2011 23:55, timezone id: 55, expected result: 3/2/2011 1:55
Finally: The Test
Scenario: date: 2/2/2011 23:55, timezone id: empty, expected result: 3/2/2011 1:55
Resources
Thank you
Panagiotis Pnevmatikatos pnevmap@gmail.com

Unit testing

  • 1.
  • 2.
  • 3.
    The Ideal testingpyramid Source: martinfowler.com
  • 4.
  • 5.
    Unit Test: adefinition A unit test is an automated piece of code that invokes a unit of work in the system and then checks a single assumption about the behavior of that unit of work. A unit of work is a single logical functional use case in the system that can be invoked by some public interface (in most cases). A unit of work can span a single method, a whole class or multiple classes working together to achieve one single logical purpose that can be verified. Source: The art of unit testing
  • 6.
    How is itdifferent that an integration test? An integration test usually is not in-memory: it may touch the disk file system, databases, registry or other shared resources. It may read or need a configuration file of the file system, whereas a unit test can be fully configured in-memory, for example. An integration test will usually run much slower because it does not run in-memory. An integration test can be less consistent – it may use threads or random number generators so that the result of the test is not always consistent. Source: Roy Osherove Blog
  • 7.
    ● Self-descriptive ● Noconditional logic, No loops ● No exception handling ● Assertions & Assertion messages ● No test logic in production code ● Test code organization & structure Unit test: best practices ● 3 Steps ● Fast ● Consistent ● Atomic & Isolated ● Single responsibility ● Environment isolation ● Classes isolation ● Fully automated
  • 8.
    3 steps -AAA ● Setup ● Prepare an input ● Call a method ● Check an output ● Tear down
  • 9.
    Fast Frequent execution ● Severaltimes per day [Test-after development] ● Several times per hour [Test driven development] ● Every few minutes [IDE - Execute after save] Execution in groups ● 100 tests = Execution time x 100 ● Weak link: a slow test slows the whole suite
  • 10.
    Multiple invocations ofthe test should consistently return true or consistently return false, provided no changes was made on code. So we cannot: DateTime currentDateTime = DateTime.Now; int currentValue = new Random().Next(); We can use Mocks and Dependency Injection (DI) Consistent
  • 11.
    Atomic & Isolated ●Only two possible results: Pass or Fail ● No partial results ● Different execution order must yield same results. ● Test B should not depend on outcome of Test A ● Use Mocks instead of Dependencies
  • 12.
    Single Responsibility One testshould be responsible for one scenario So we test behavior not methods ● 1 methdod, n behaviors → n tests ● n methods, 1 behavior → 1 test
  • 13.
    Environment Isolation Unit testsshould be isolated from an environmental influences ● Database ● Web services ● Environment variables ● Property files ● System date/time
  • 14.
    Environment Isolation In orderto achieve Environment Isolation we use Mocks and Fakes ● Handwritten mock / fake objects ● Isolation Frameworks (RhinoMocks, NSubstitute etc)
  • 15.
    Goals ● Minimal methodcalls in a test ● Minimal tests per method ● High code coverage Anti-patterns ● Constrained test order ● Hidden test call ● Shared state corruption (bad set up or teardown, use of singletons or static instances) Classes Isolation
  • 16.
    Fully automated Automated execution Automateddecision making (success or failure) IDE Integration
  • 17.
    Self-descriptive A Unit test ●Is development level documentation ● Ensures that method specs are up to date ● Naming template: Method_Scenario_ExpectedBehavior() or Method_State_ExpectedBehavior() ● Test only one thing ● Meaningful assert message ● Separating Assert from Actions (Wrong: Assert.AreEqual(COULD_NOT_READ_FILE,log.GetLineCount("abc.txt"));)
  • 18.
    No conditional logic,No loops No conditional logic ● Conditional logic reveals different behavior ● All inputs should be known ● Expected output should be strictly defined ● Instead of “if” or “switch” we split into two or more tests No loops ● If we need to repeat the test logic then it's too complicated
  • 19.
    No Exception Handling ●Catch only the expected type of exception ● Fail test if expected exception is not caught ● Let all other exceptions uncaught
  • 20.
    Assertions & Assertionmessages ● Extend Assert with custom assertions (object comparer, repetitive conditions etc) ● Expressive message (reading the message only should be able to recognize the problem) ● Include business logic info (input values etc.) ● Consider assert messages as code documentation
  • 21.
    No test logicin production code Separate projects for production code and tests Do not create methods/fields/properties only for testing Dependency Injection
  • 22.
    Test code organization& structure ● Source control ● File structure ○ One test class per feature or ○ One test class per class under test ● Test code structure ○ Abstractions ○ Inheritance ○ Template class ○ Generics
  • 23.
  • 24.
    Design for testability ●Make methods virtual ● Interface-based design ● Non Sealed classes ● Do not instantiate classes inside methods ● Do not call static methods directly ● Do not use static constructors ● Do not use construction logic ● Separate singletons and singleton holders
  • 25.
    Pros ● Clean code ●Safer refactoring ● Decoupled code ● Documentation of requirements ● Cheaper maintenance Cons ● Amount of work ● Complexity ● Exposing sensitive info ● Sometimes you can’t Pros & cons
  • 26.
    ● The Liar(test passes but not really testing) ● Excessive Setup (might be integration test?) ● The Giant (alt to God Class) ● The Mockery (similar to Excessive Setup) ● The Inspector ● Generous Leftovers ● The Local Hero (dev environment dependencies) ● The Nitpicker (asseting unimportant details) ● The Secret Catcher Unit Testing Anti-patterns ● The Sequencer (order dependency) ● Hidden Dependency ● The Enumerator (bad naming) ● The Stranger (not relative to the case) ● The Distant Relative ● The Operating System Evangelist ● Success Against All Odds ● The Free Ride (stuffing assertions) ● The One (Giant & Free Ride)
  • 27.
  • 28.
    Legacy code Definition: Legacycode is the code without tests The Legacy Code Dilemma: When we change code, we should have tests in place. To put tests in place, we often have to change code.
  • 29.
    The Legacy CodeChange Algorithm 1. Identify change points 2. Find test points 3. Break dependencies 4. Write tests 5. Make changes and refactor
  • 30.
    Legacy code Where tostart adding tests? Rate every component for ● Logical complexity ● Dependency level ● Priority
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
    Real world example:Unit Test Here we can discuss about the need of this unit test and if the “unit” should follow the creative or defensive programming strategy and trust the input or not!
  • 38.
    Real world example:Unit Dependencies Before we test we must obviously isolate the unit from CookieHelper and TimeZoneInfo dependencies. We can also safely refactor the unit by breaking it into two pieces.
  • 39.
    A simple saferefactoring: extract method with Resharper
  • 40.
    A simple saferefactoring: extract method with Resharper
  • 41.
  • 42.
    Breaking the dependencieswith Dependency Injection 1) Use the Bridge design pattern to separate a class's interface from its implementation so you can vary or replace the implementation without changing the client code
  • 43.
    Breaking the dependencieswith Dependency Injection Bridging the CookieHelper and TimeZoneInfo classes
  • 44.
    Breaking the dependencieswith Dependency Injection 2) Create a non-static DateExtensions class and Inject Dependencies
  • 45.
    Finally: The Test Scenario:date: 2/2/2011 23:55, timezone id: 55, expected result: 3/2/2011 1:55
  • 46.
    Finally: The Test Scenario:date: 2/2/2011 23:55, timezone id: empty, expected result: 3/2/2011 1:55
  • 47.
  • 48.