What To and Not To Test – Mark Seemann

Blog Post:

https://blog.ploeh.dk/2018/11/12/what-to-test-and-not-to-test/

The Purpose of Testing

  • To exercise the Api and the design. A hard class to test, is a poorly designed interface.
  • To prevent regressions.

The Cost of Regressions

Dimensions of the Risk

  • The likelihood the event.
  • The impact of the event.

To reduce risk, you either decrease the likelihood or the impact of the event.

What is the impact of the error if happens? If it’s a dev app, there is almost zero risk. If it’s a Voyager probes. It could doom the project.

Udemy – TDD Class

F.I.R.S.T

  • Fast
  • Independent
  • Repeatable
  • Self-Validatable
  • In-Time, written before production code

Types of Tests

  • Unit Tests – verify the behavior of a unit under tests in isolation
  • Integration Tests – verify the behavior of either a part of the system or a whole system. These tests are often brittle.
  • Acceptance Tests – verify the software from the user’s point of view.

Frameworks & Tools

  • SpecFlow – BDD
  • xUnit – unit testing
  • Moq – Mocking
  • NCrunch
  • Resharper

Notes

  • Triangulation
    • We generalize the implementation when we have two or more test cases. We add test cases until the right way of implementation starts to emerge.
  • Faking
    • Faking means returning constants and gradually replacing the value with variables and evolving the solution.
  • Obvious Implementation
    • Sometimes the implementation appears obvious, but as you start to poke at it with tests, it begin to crumble.
  • Grabbing the gold
    • You start from writing unit tests which cover the core functionality right away, and it fails with edge cases.
    • Suggestion: Write as many tests as possible before approaching the core functionality. Check for max values, empty strings, null values, min values… etc.
    • Uncle Bob’s Suggestions: Write tests exactly in the following order: exceptional, degenerate, and ancillary
      • Degenerative cases are those which cause the core functionality to do "nothing"
      • Ancillary behaviors are those which support the core functionality

Tactical Design Patterns

Apply the design pattern when the design is ready to accept it.

  • Don’t force a design pattern onto a problem. Let the code evolve until it’s obvious the design pattern is needed. It is better to be late than early.

  • This idea comes back to my thoughts on keep things simple don’t over architect code. Don’t add it, until it’s a requirement.

Multiple Iterations

  • The first attempt is to produce the tailored design
    • Design patterns do not fit into this phase
  • Then refactor to reach a better design
    • this is where design patterns fit well.

Principle of Least Surprise

Design Patterns are generally more complex than they are in the examples and text books.

Refactoring

A change made to the internal structure of software to make it easier to understand and cheaper to modify without changing its observable behavior.

Any fool can write code that a computer can understand. Good programmers write code that humans can understand. – Martin Fowler

Technical debt is the sum of the following coding shortcuts:

  • Duplication
  • Excess Coupling
  • Quick Fixes
  • Hacks

Why Refactor?

  • Improves Design
  • Improves Readability
  • Reveals Defects
  • Helps You Program Faster

Other than when you are very close to a deadline, however, you should not put off refactoring because you haven’t got time. Experience with several projects has shown that a bout of refactoring results in increased productivity. Not having enough time usually is a sign that you need to do some refactoring. – Martin Fowler

Refactoring Principles

  • Keep it Simple
  • Keep it DRY
  • Make It Expressive
  • Reduce Overall Code
  • Separate Concerns
  • Appropriate Level of Abstraction

Refactor Tips

  • Keep Refactoring’s Small
  • One At a Time
  • Make a Checklist
  • Make a ‘later’ list
  • Check in Frequently
  • Add Test Cases
  • Review the Results

Premature optimization is the root of all software evil. – Donald Knuth

Coding Philosophy

  • Keep it simple.
  • Deliver value.
  • Avoid over-engineering.
  • Avoid gold plating your code.

Principle of Least Surprise

  • Do what users expect
  • Be Simple
  • Be Clear
  • Be Consistent
  • Design API to behave as programmers would expect.

Organizing Code Smells into 5 groups

  • The Bloaters
    • Do it all god objects…
      • Small lean code bases and changed quickly
    • Long Methods
      • Keep methods smaller
        • 10 lines or fewer
        • Avoid regions
  • The Object-Orientation Abusers
  • The Change Preventers
  • The Dispensables
  • The Couplers
  • The obfuscators
  • Environment Smells
  • Test Smells
  • Architecture Smells

Refactoring Long Methods

  • Extract Method
    • Introduce Parameter Object
    • Replace Temp with Query
    • Replace Method with Method Object
  • Compose Method
    • Place bits of functionality into smaller methods.
    • The result is a method that has a number of method calls.
  • Replace Nested Conditional with Guard Clause
  • Replace Conditional Dispatcher(switch statement) with Command Pattern
  • Move Accumulation to Visitor ()
  • Replace Conditional Logic Strategy pattern

Refactor Large Class

Violating Single Responsibility Principle

Too many instance variables

Too many private methods (Iceberg class)

Lack of cohesion

Some methods work with some instance variables, others with others

Compartmentalized class

Refactoring Large Classes Strategy

Extract Method (and hopefully combine logic)

Extract class

Extract Subclass/Extract Interface

Replace Conditional Dispatch with Command

Replace State-Altering Conditionals with State Machine

Replace Implicit Language with Interpreter Pattern

Primitive Obsession

  • Over-use of primitives, instead of better abstractions, results in excess code to shoehorn the type. The primitives themselves can contain invalid states and therefore must be checked when the value is set or used.

    • Often Guard clauses and validation is used to everytime the value changes for the type.
    • Les Intention – Revealing code
  • Refactor away from Primitive Obsession

    • Replace Data value with Object
    • Replace Type Code with Class
    • Replace Type Code with Subclass
    • Extract Class
    • Introduce Parameter Object
    • Replace Array with Object
    • Replace State-Altering Conditionals with State
    • Replace Condition Logic with Strategy
  • Examples of a Primitive Data Obsession

    • ZIP / Postal Codes
    • Phone Numbers
    • Social Security Numbers
    • Telephone Numbers
    • Money
    • Age
    • Temperature
    • Address
    • Credit Card Information

Long Parameter List

  • May indicate procedural rather than OO programming style

  • Related Smells

    • Message Chains
    • MiddleMan
    • Rules of Thumb
      • No more than three arguments
      • No output arguments
        • Not intuitive
      • No flag arguments
        • means the method is doing more than one things
      • Selector arguments
        • MEans the method is doing more than one thing.
  • In general

    • Prefer more, smaller, well-named methods to fewer methods with behavior controlled by parameters

Writing Allocation Free Code in C# By Matt Ellis

Twitter: @citizenmatt
GitHub: https://github.com/citizenmatt/RefSemantics

Always be measuring – don’t do it because you think it’s the right thing to do. It might not be.

Managed memory is cheap. That’s not true, allocation is cheap and garbage collection is expensive. Garbage collection stops the world while things are cleaned up and optimized. Sometimes memory cleanup can impact the performance of the application.

Performance Low Hanging Fruit

Object reuse

Object pooling. Pass in existing array rather than allocating a new one. Preallocate array length.

String concatenation.

Use StringBuilder, Preallocate length if possible.

params arguments

void MyParamsMethod(params string[] args)
{

//... 

}

In Code:
MyParamsMethod("Hello", "World");
MyParamsMethod();

Complier:
MyParamsMethod(new {"Hello", "World"});
MyParamsMethod(new [0]); // this creates a new array object in memory

Instead, do this in code:
MyParamsMethod(new {"Hello", "World"});
MyParamsMethod(Array.Empty); // reuses an existing empty string array in memory. It's the same idea behind string.Empty

Suggestion: Introduce overloads with common number of arguments.

Boxing

Boxing creates a new object on the heap. Now you have two memory locations with the same value. Changing either value does not impact the other value.

Suggestion: Introduce generic overloads

Closures

The complier converts closures to classes. Captured values are passed in as constructor parameters. This class is then allocated to the heap.

Suggestion: Avoid critical paths. Pass state as argument to lambda. Investigate local functions. Since local functions lifetime is known, it can be allocated on the stack where allocation and cleanup are cheap.

LINQ

Lambda expressions are treated the same as Closures. They are allocated as a class to the heap. Because much of LINQ is based on static methods, additional allocations of Iterators and IEnumerable happen.

Suggestion: Avoid critical paths. Use good old foreach and if statements

Iterators

iterators are rewritten as a state machine, which means more allocations to the heap.

Suggestion: Return a collection. Be aware of the cost.

async/await

Async/await also, generate a state machine. Task and Task also trigger more allocations which then can’t be reused.

Look into ValueTask for common uses cases.

Suggestion: Investigate ValueTask.

Heap Vs Stack

Each method pushes space onto the stack for local variables. When the method is exited the memory is popped from the stack.

Stack allocation and cleanup are cheap because the memory has a lifetime. However, stack space is limited.

*Why passing value types copies the data versus passing a references it’s important to note that copying data isn’t that expensive.

Reference Semantics with Value Types

Allows value types to be used like reference types
– pass by reference everywhere

User value types to reduce allocations, reduce memory traffic, etc
– Throughput!

Pass by reference to avoid copies, enables modifying, etc.

Very low-level micro-optimizations…
– But they’ll be used in the platform…
– (And games, and parsing, and serialization, and …)

*These are low level optimizations that should only be used in paths were performance matters.

C# 7.2 Reference Semantics with Value Types

Allocating a reference type has a cost, but passing it around is cheap.
Allocating a value type is cheap, but passing it around has a cost.

Why can’t it be cheap to allocate AND cheap to pass around?

in parameters

Pass value type by reference. Called methods cannot modify it.

Method argument modifier

Complements out and ref

Passed by reference

Method cannot modify original value

Compiler enforces safety with defensive copy when calling members

ref locals and ref returns (C# 7.0)

ref returns

Returns a reference to value the, not a copy of the value.

– return type of method becomes e.g. integer reference int& in IL

Lifetime of returned value must exceed the lifetime of the called method

  • e.g. a reference to a field or method argument. NOT a variable in the called method.
  • Not allowed on async methods.

Modifying this reference is the same as modifying the original value

  • e.g. return reference to array element, and update it in place.

Add ref modifier to method declaration return type, and to return statement

ref locals

Assign a ref return to a new variable will create a copy

  • The variable is a value type, not a reference. (Cannot assign int& to int)
  • A ref local is a variable that is a reference to a value type
  • Accessing the variable accesses the original value

Use a ref local to store the ref return result

Type inference with var will get the value type, not the ref modifier

  • Requires ref var to work as expected.

ref readonly returns

Returns a read only value type by reference

readonly struct

Immutable value types

in parameters and ref readonly can create defensive copies

  • The compiler doesn’t know if the struct’s methods will modify state

readonly struct – compiler enforces all fields and properties are readonly

Immutable

More efficient – no copies made when calling members

  • Improves performance (micro-optimization)

ref struct

Stack only value types

Declare a value type that can only be stack allocated

  • I.e can never be part of a reference type

This constrains lifetime to calling method

  • Also, cannot be boxed, cannot use inside a non-ref struct
  • Cannot use with async methods or iterators
  • Cannot be a generic parameters

Limited use cases

  • Working with stackalloc memory
  • Primarily for Span<T>

What does SPAN have to do with ref struct?

For thread safety, need to update all fields of Span atomically (tearing)

  • Whole point is performances – cannot use synchronization

Internal pointers require special GC tracking

  • Too many in flight at once is expensive

How can SPAN represent stackalloc memory is SPAN was on the heap?

Solution: Span<T> is a ref struct – can only be created on the stack

  • Constrained lifetime, single thread access

Span<T>

New type of unify working with any kind of contiguous memory

  • Arrays, array segments, strings and substrings, native memory, stackalloc, etc

Provides array-like API – indexer

  • ReadOnlySpan provides getter indexer only

Type safe – each elements is of type T

Array-like performance

  • Not quite, but newer runtimes have special support

Slicing

  • Create a new Span with a sub-section of existing – without allocations!

Span<T> Implementation

Value Type – struct

System.Memory NuGet package

  • .NET Standard 1.1 (.NET Framework 4.5)+

New APIs and overloads in the BCL

  • E.g. String.AsSpan(), Stream.ReadAsync(), Utf8Parser.TryParse()
  • Significant usage of ref semantics – allocation free!

Span, ReadOnlySpan, Memory

Two versions – “portable” and “fast”

  • fast requires runtime support

Span<T> Performance – Portable Implementation

Portable works on .NET Standard 1.1 and above

  • .Net Framework 4.5+

Portable is not slow

  • But not as fast as arrays

Three fields – object reference, internal offset and length

  • Slightly larger than fast version, dereferencing is slightly more complex operation.

Span<T> Performance – Fast Implementation

Fast requires runtime support

  • .Net Core 2.1

Only has two fields – “byref” internal pointer and length

  • Slightly smaller struct and accessing an element is slightly simpler operation

Specific JIT optimizations

  • e.g elding bounds check in loop, like arrays

Very close to array performance