Different flavors of TDD

@kuba_ciechowski

Canonical TDD

@kuba_ciechowski

Red

Red

Green

Refactor

test && commit || revert

Vending machine kata

  • Insert and return money
  • Can buy a product without change
  • Can buy a product with change
  • Specific number of products is available
[Theory]
[InlineData("1.00, 0.50", "1.00, 0.50")]
[InlineData("0.50, 0.50", "0.50, 0.50")]
[InlineData("0.25, 0.25", "0.25, 0.25")]
public void ReturnsAllInsertedCoins(string insertedCoins, string expected)
{
  var vendingMachine = new VendingMachine();
  vendingMachine.InsertMoney(insertedCoins);

  var returnedCoins = vendingMachine.Return();

  returnedCoins.Should().Be(expected);
}
[Theory]
[InlineData("0.50, 0.50", "0.25")]
[InlineData("1", "0.25")]
public void GetCandyWithChange(string insertedCoins, string expectedChange)
{
  var vendingMachine = new VendingMachine();
  vendingMachine.InsertMoney(insertedCoins);
  
  var result = vendingMachine.GetCandy();
  
  result.Should().ContainAll("Candy", expectedChange);
}
  • Easy to write
  • Fast feedback
  • Easy to pinpoint bug
  • Improves API
  • Can make refactoring hard
  • Usually tests implementation details
  • Tests specific cases

BDD

@kuba_ciechowski

Behavior Driven Development

  • Test business scenarios
  • Given -> When -> Then
  • "Outside In" instead of "Inside Out"
namespace BuyingProductsWithoutChange
{
  public class WhenInsertedEnoughMoneyForCola 
      : GivenSubject<VendingMachine, string>
  {
    public WhenInsertedEnoughMoneyForCola()
    {
       Given(() => Subject.InsertMoney("1"));
       When(() => Subject.GetCola());
    }

    [Fact]
    public void ThenGetCola()
    {
       Result.Should().Contain("Cola");
    }
  }
}
namespace BuyingProductsWithChange
{
  public class WhenInsertedMoreMoneyThanPriceOfProduct 
    : GivenSubject<VendingMachine, string>
  {
    public WhenInsertedMoreMoneyThanPriceOfProduct()
    {
       Given(() => Subject.InsertMoney("1, 0.50"));
       When(() => Subject.GetCola());
    }

    [Fact]
    public void ThenGetColaWithChange()
    {
       Result.Should().ContainAll("Cola", "0.50");
    }
  }
}

BDD is TDD done right

  • Test business cases
  • Outside in approach
  • Improves API
  • Can be slow
  • Can be hard to write

Mutation Testing

@kuba_ciechowski

Mutate

Run tests

Verify

Mutation testing flow

 

Stryker .Net

Mutants, I don't hate them.

I just know what they can do.

Code Coverage

HTML report

Useless mutants

Useful mutants

  • Easy to find uncovered cases
  • Slow
  • Can't run it on solution

  • Not many mutators yet

Property Based Testing

@kuba_ciechowski

What is Property Based Testing?

The thing that QuickCheck does

PBT approach

  • Should hold for range of data points
  • Data points can vary
  • Requires defined properties

Testable properties

  • Identity
  • Transforming data (e.g. message to fit SMS)
  • Calculating price with discounts
  • Follow fixed algorithm
internal static class CoinsGenerator
{
  private static readonly decimal[] AllowedCoins = {1m, 0.50m, 0.25m};
  public static Arbitrary<string> CoinGenerator()
  {
    return Gen.Elements(AllowedCoins)
            .ListOf()
            .Select(x => string.Join(", ", x))
            .ToArbitrary();
  }
}

[1, 0.5, 0.5]

[Property(Arbitrary = new[] {typeof(CoinsGenerator)})]
public Property ReturnInsertedMoney(string coins)
{
  var vendingMachine = new VendingMachine();
  Func<bool> property = () =>
  {
    vendingMachine.InsertMoney(coins);
    return vendingMachine.Return() == coins;
  };

  return property.ToProperty();
}
internal class MoreThan1DollarGenerator
{
    private static readonly decimal[] AllowedCoins = {1m, 0.5m, 0.25m};
    public static Arbitrary<(string,string)> MoreThan1D()
    {
        return Gen.Elements(AllowedCoins)
            .ListOf()
            .Where(x => x.Sum() > 1.0m)
            .Select(x => 
                (string.Join(", ", x), (x.Sum() - 1.0m).ToString()))
            .ToArbitrary();
    }
}

([1, 0.5, 0.5]; 1)

[Property(Arbitrary = new[] {typeof(MoreThan1DollarGenerator)})]
public Property CanBuyProductWithChange(
  (string coins, string change) coinsWithChange)
{
    var vendingMachine = new VendingMachine();
    var (coins, change) = coinsWithChange;
    Func<bool> property = () =>
    {
      vendingMachine.InsertMoney(coins);
      var boughtColaWithChange = vendingMachine.GetCola();
      return boughtColaWithChange.Contains("Cola") 
        && boughtColaWithChange.Contains(change);
    };

    return property.ToProperty();
}

So slooooow

And we have a fail!

  • Tests a lot of data points
  • Random values can find bugs
  • Require different mindset
  • Unpopular
  • Require upskilling
  • Cumbersome in C#

Summary

  • Use every kind of tests
  • Make it fast
  • Make it easy to diagnose
  • Give TDD a try

@kuba_ciechowski

Links

@kuba_ciechowski

Made with Slides.com