Enterprise development (in .NET), Day 6
Practical
Unit-Testing
Andrey Shchekin
http://ashmind.com
Sankt-Petersburg, 2009
Images © Mattahan Productions, Inc | http://mattahan.deviantart.com/ | http://mattahan.insocada.com/
Why
Complex systems
are hard to change
You can not prevent it. But tests
help to find problems.
In time.
Why tests?
Fast feedback on new code
Safeguards for old code
Exploration for external code
Like a personal tester, for free
You already do manual tests
Automatic = manual +
1. fast feedback
2. no regression
3. simple coverage
1 000 tests = 1 000 bugs not in
the current version
Code is no longer a
minefield
What
Unit
Functional
Integration
Acceptance Tests
Performance
Load
Stress
Unit tests
Written by developers
On smallest units of code
On functionality
Independent
Repeatable
Unit tests are just some
methods run by a tool.
Code
[TestFixture]
public class Int32Test {
[Test]
public void TestParse() {
var result = int.Parse("5");
Assert.AreEqual(5, result);
}
}
Tool
Unit Test Magic
1. Attributes
2. Assertions
3. Mocks
Attributes
[TestFixture] == [TestClass]
[Test] == [TestMethod]
// other:
[Ignore]
[ExpectedException]
[SetUp] && [TearDown]
Assertions
[Test]
public void TestAverage() {
var result =
Mathematics.Average(5, 10, 2, 7);
Assert.AreEqual(6, result);
}
Assertions
Assert.AreEqual(…)
Assert.AreSame(…)
Assert.Between(…)
Assert.LowerThan(…)
Assert.IsNotNull(…)
Assert.IsFalse(…)
…
No magic: they just throw AssertionException().
How
Good test / Bad test
Name-based guess.
Bad
TestProcessOrder
Good
TestProcessOrderCalculatesTotalValueCorrectly
TestProcessOrderNotifiesCustomer
TestProcessOrderChangesOrderStatusToProcessed
Test-Driven Development
1. Write new business code only when an
automated test has failed.
2. Eliminate any duplication that you find.
Kent Beck
Test-Driven Development
red green refactor
Guidelines
Always add a test for a bug, before fixing it.
When touching legacy code, start with tests.
Use TDD for complex stuff (ex: regexps).
Be simple.
Be focused (assert one thing).
Run tests! Do it often.
Good tests require
Separation of Concerns
Single Responsibility Principle
Each class should do just one thing.
Test Frameworks
NUnit
Gallio (MbUnit)
VS UT Framework
xUnit.Net
NUnit
First UT framework for .NET.
Has all basic features.
Slow to evolve.
http://www.nunit.org
Gallio (MbUnit)
Might be the best testing framework.
Highlights:
1. Row testing
2. Type testing
3. Parallel testing
4. …
http://www.mbunit.com
Collection Assertions
[Test]
public void TestListAdd()
{
var list = new List();
list.Add(3);
list.Add(5);
Assert.AreElementsEqual(new[] {3, 5}, list);
}
Exception Assertions
[Test]
public void TestDictionatyThrowsKeyNotFoundWhenNotFound()
{
var dictionary = new Dictionary();
Assert.Throws(
() => dictionary["nosuchkey"]
);
}
Row Testing
[Test]
[Row(5, 10, 3, 10)]
[Row(-3, -5, 0, 0)]
public void TestMaximum (
int first, int second, int third, int expected
)
{
var result =
Mathematics.Maximum(first, second, third);
Assert.AreEqual(expected, result);
}
VS UT Framework
Microsoft’s testing framework.
Not extensible, no [RowTest].
Uses [TestClass], [TestMethod].
Has good integration with Visual Studio.
http://msdn2.microsoft.com/en-us/library/ms182409(VS.80).aspx
xUnit.NET
New project by the creator of NUnit.
Workflow changes.
No [SetUp] and [TearDown]
Different attributes
Might be good replacement for MbUnit.
http://www.codeplex.com/xunit
Mocking
State testing
?
?
Is the resulting data correct?
Behavior testing
?
Was there the call we expected?
Nice Architecture
Business Rules
Data Access User Interface
Repositories
Business Rules
Mock objects Business Rules
class Database {
double GetSalary(int id);
int GetLengthOfWork(int id);
}
class Calculator {
double Calculate13thSalary(int id);
}
class Database : IDatabase
interface IDatabase {
double GetSalary(int id);
int GetLengthOfWork(int id);
}
class Calculator {
Calculator(IDatabase database);
double Calculate13thSalary(int id);
}
Mock Types
Static
hand-written
auto-generated in code
Dynamic
auto-generated at runtime
Dynamic Mock Advantages
Mocking selected methods
Built-in verification system
Test-level setup
Less code!
Setup
[Test]
[Row(2000, 2, 800)]
[Row(1000, 3, 600)]
public void TestCalculateReturnsExpectedResult(
double salary, int length, double expected
)
{
var testId = 1;
var mock = new Mock();
mock.Setup(x => x.GetSalary(testId))
.Returns(salary);
mock.Setup(x => x.GetLengthOfWork(testId))
.Returns(length);
var result = new Calculator(mock.Object).Calculate13thSalary(testId);
Assert.AreEqual(expected, result, 0.01);
}
Setup
mock.Setup(x => x.GetSalary())
.Returns(salary);
mock.Setup(x => x.GetLengthOfWork())
.Returns(lengthOfWork);
Mocking: 3A workflow
Arrange
Act
Assert
Arrange
var mock = new Mock();
mock.Setup(x => x.GetSalary(testId))
.Returns(salary);
mock.Setup(x => x.GetLengthOfWork(testId))
.Returns(length);
Act
var result = new Calculator(mock)
.Calculate13thSalary(testId);
Assert
Assert.AreEqual(expected, result, 0.01);
Arrange
var order = new Order(…) {…};
var mock = new Mock();
Act
new OrderProcessor(mock.Object).Process(order);
Assert
mock.Verify(x => x.Send(…, order.Customer));
Mock Frameworks
Moq
RhinoMocks
TypeMock.NET
Moq
The one I use.
Simple and convenient, but requires .NET 3.5.
mock.Setup(x => x.GetSalary(testId))
.Returns(salary);
http://moq.me/
RhinoMocks
Older than Moq, covers a lot of edge cases.
Complex API, but works on .NET 2.0.
Expect.Call(mock.GetSalary(testId))
.Return(salary);
http://www.ayende.com/projects/rhino-mocks.aspx
TypeMock.NET
Commercial mocking framework.
Virtual Mock concept:
Can mock concrete classes, static methods, etc.
May be useful with legacy code.
http://www.typemock.com/
Coverage
Coverage is a measure on
how much of your code is
tested.
Tools
NCover
Best coverage tool for .NET
Commercial, free version exist (but limited).
PartCover
Free, but less features than NCover
Coverage
Is a formal metric
Does not equal "well-tested"
However, coverage < 75% may
mean something is wrong
Bonus
Knowledge
Pex
Microsoft Research project.
Automatically generates tests for source code.
[PexTest]
public void TestCalculator(
double salary,
int stage,
double expected
)
http://research.microsoft.com/Pex/
Start now
Gallio: http://www.gallio.org/Downloads.aspx
Moq: http://moq.me/downloads/list
Questions