Skip to content

Instantly share code, notes, and snippets.

@andrewdavey
Created May 25, 2011 09:53
Show Gist options
  • Save andrewdavey/990690 to your computer and use it in GitHub Desktop.
Save andrewdavey/990690 to your computer and use it in GitHub Desktop.
Different approach to C# unit testing
// Sorry to stir things up, but I really don't get on with the current *Unit test frameworks
// in C#. I hate creating_long_semi_english method names. I hate then repeating myself
// in code e.g. public void It_sould_equal_10() { Assert.That(it == 10); }
// I think the problem stems from making a method the atom of a test.
// This leads to crap method names and hard to understand tests.
// What follows is a different approach. (Not real code yet, just ideas!)
// One Test class per class under test.
// Inherits framework `Tests` class to provide test-DSL methods.
class AccountTests : Tests {
// One test method per specification i.e. method under test.
// Multiple scenarios a defined within.
public void Creation() {
// Given -> Then (no When needed here for simple constructor tests).
// Given establishes the context/subject-under-test
// Then takes an Expression<Func<T, bool>>
// * it can create a decent human readable string.
// * it compiles and executes as an assertion.
Given(new Account()).Then(the => the.Balance == 0);
Given(new Account(100)).Then(the => the.Balance == 100);
// Then can also take a more general 'assertion' object
// seen here to check for exceptions.
Given(new Account(-1)).Then(Expect.Exception<NegativeBalanceException>());
// These test helper methods are building up a collection of scenarios to be run.
// They don't actually run when this method is called. A runner will collect
// all the scenarios and execute them later.
}
public void Deposit() {
// Can provide a string to describe the context better.
Given("An empty account", new Account())
.When(a => a.Deposit(100))
.Then(a => a.Balance == 100);
}
public void Withdraw() {
Given(new Account(100))
.When(a => a.Withdraw(50))
.Then(a => a.Balance == 50);
Given(new Account(100))
.When(a => a.Withdraw(200))
.Then(Expect.Exception<InsufficientFundsException>());
}
public void Reward() {
// A more complex 'context' can be established.
// For example, creating mocks and other helper objects.
Given(delegate {
var context = new {
account = new Account(),
bonusCalc = new Mock<IBonusCalc>()
};
context.bonusCalc.Setup(c => c.GetBonus()).Returns(42);
return context;
}).
When(c => c.account.Reward(c.bonusCalc.Object)).
Then(c => c.account.Balance == 42);
}
// ** Other ideas **
// Clean up using a .Finally(<action>) method call.
// Given(context).When(action).Then(assertion).Finally(clean_up);
// Parameterization is now freaking easy. Just call Given/When/Then in a loop!
// foreach (var row in data) {
// Given(context).When(c => c.Process(row.Input)).Then((result,c) => result == row.Output);
// }
// Nest contexts to reduce duplication
}
@wangvnn
Copy link

wangvnn commented Dec 18, 2016

There is a beautiful thing like this in .net world, but it is under-developed:
https://github.com/fschwiet/DreamNJasmine
My example: https://github.com/wangvnn/TheDCIBabyIDE/blob/master/TheDCIBabyIDE.Test/Example.cs
Note: I want nested when (Given A, When B, Then C, Given A, When B, When B' Then C' ...) without creating reused code...)
And my favourite: https://github.com/philsquared/Catch/blob/master/docs/tutorial.md

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment