7. What’s not a unit test?
I like Michael Feathers’ explanation from Working Effectively with Legacy Code.
It’s not a unit test if:
• It talks to a database
• It communicates across a network
• It touches the file system
• You have to do special things to your environment to run it (like editing a
config file)
8. DIFFICULT
SCENARIOS TO
UNIT TEST
Closed Object Models
(Sharepoint, Silverlight)
Client – server architecture
- Communicating Across a Network
An out-of-process call
- Includes talking to databases and
Web Services
UI/GUI interaction
Touching the File System
Legacy Code
Requires the Environment to be
configured
11. “When you first start at doing TDD you know what the design should be. You
know what code you want to write. So you write a test that will let you write that
bit of code.
“When you do this you aren't really doing TDD – since you are writing the code
first (even if the code is only in your head )
“It takes some time to realize that you need to focus on the test. Write the test
for the behavior you want - then write the minimal code needed to make it pass
- then let the design emerge through refactoring. Repeat until done.”
--Brian Rasmussen
http://www.slideshare.net/baronslideshare/testdriven-development-tdd
12. An Exercise in
Test Driven
Development
The idea here is going to be to go
through the exercise of designing a
portion of a new solution in real
time.
After the exercise I want to circle
back and point out the benefits we
gained from the test first approach
which may have been missed had
we coded and then wrote tests.
13. Principles
• Think About What You Are Trying To Do
• Follow The TDD Cycle
• Never Write New Functionality Without A
Failing Test
- Each test should be for a single
concept
• Continually Make Small, Incremental
Changes
• Keep The System Running At All Times
- No one can make a change that breaks the
system
- Failures Must Be Address Immediately
• Is an activity of the developer
• Test code should be treated the same as
“production”
- maintained
- refactor
- source control
• Experimentation
- Names don’t matter initially
• When you want to change existing code,
first write covering, white box, unit tests
and then use TDD to add new
functionality
• Fix it in the simplest way possible using
as many assumptions as you like
15. Realizing quality improvement through test driven development
Metric Description IBM:
Drivers
MSFT:
Windows
MSFT:
MSN
MSFT:
VS
Defect density of
non-TDD team
W X Y Z
Defect density of
team using TDD
0.61W 0.38X 0.24Y 0.09Z
% increase in
coding time
because
of TDD
[Management
estimates]
15-20% 25-35% 15% 25-30%
http://research.microsoft.com/en-us/groups/ese/nagappan_tdd.pdf
18. What’s a mock object?
A Test Double object that is pre-programmed with expectations which form a
specification of the calls they are expected to receive
The nice thing about using mocks while doing TDD is we can mock something
that doesn’t actually exist. Just define an interface and mock that. Later on we
can create concrete implementations of the interface (through TDD, of course)
19. Chicago vs London
Ledger
● Calculate(string expression)
Calculator
● Add
● Subtract
● Multiply
● ...
Based on http://programmers.stackexchange.com/a/123672
ledger.Calculate("5 * 7")
20. [TestFixture]
public class LedgerTests
{
public static Ledger Ledger { set; get; }
public static int Value { get; set; }
public static Mock<ICalculator> calculator;
[SetUp]
public void LedgerSetup()
{
calculator = new Mock<ICalculator>();
Ledger = new Ledger(calculator.Object);
}
21. Chicago vs London
[Test]
public void ChicagoStyle()
{
Value = Ledger.Calculate("5 * 7");
Assert.AreEqual(35, Value);
}
The Chicago/State school would have you assert whether the result is 35. The jUnit/nUnit frameworks are generally geared
towards doing this.
22. Chicago vs London
[Test]
public void ChicagoStyle()
{
Value = Ledger.Calculate("5 * 7");
Assert.AreEqual(35, Value);
}
The Chicago/State school would have you assert whether the result is 35. The jUnit/nUnit frameworks are generally geared
towards doing this.
[Test]
public void LondonStyle()
{
calculator.Verify(calc => calc.Multiply(5, 7), Times.Exactly(1));
}
The London/Interaction school would have you assert whether Calculator.Multiply(5,7) got called. The various
mocking frameworks are useful for this, and it can be very useful if, for example, you don't have ownership of the "Calculator"
object (suppose it is an external component or service that you cannot test directly, but you do know you have to call in a
particular way).
24. Other resources mentioned by attendees
The following resources were mentioned after the presentation. They require
purchase or subscription.
• Play by Play: TDD with Brad Wilson (Pluralsight)
• https://www.pluralsight.com/courses/play-by-play-wilson-tdd
• Automated Testing for Fraidy Cats Like Me with Julie Lerman (Pluralsight)
• https://www.pluralsight.com/courses/automated-testing-fraidy-cats
• Test-Driven Development training by Mike Hill & Joshua Kerievsky (Industrial
Logic)
• https://elearning.industriallogic.com/gh/submit?Action=AlbumContentsAction&album=before
&devLanguage=Java
Notas del editor
I’m coming at this as a fellow traveller - not an expert
Understand the differences between automated testing and test driven development
Understand where the value of test driven development lies
Introduction to the Moq framework
Error localization: “As tests get further from what they test, it’s harder to determine what a test failure means.” Michael Feathers Working Effectively with Legacy Code
Execution time: Large tests take longer to run. Longer to run means not run as frequently (if at all)
Tighter feedback loop: related to execution time - don’t have to wait as long to know if you’re code changes broke anything
Coverage: It’s easier to test the different execution paths in a unit test. Think about testing a new feature in a large, existing end-to-end test versus adding a unit test to exercise it
“Maintenance fixes and “small” code changes may be nearly 40 times more error prone than new development (Managing the Software Process, Watts S. Humphrey, Addison-Wesley, 1989)... By continuously running automated test cases, one can find out whether a change breaks the existing system early on, rather than leading to a late discovery.” - Realizing quality improvement through test driven development: results and experiences of four industrial teams, Nachiappan Nagappan & E. Michael Maximilien & Thirumalesh Bhat & Laurie Williams, Springer Science + Business Media, LLC, 2008
Tests like these aren’t bad. There may be value in writing them. They just aren’t unit tests.
Feathers’ definition of a unit test is driven by two properties:
Does the test run fast?
A unit test that takes 1/10th of a second is too long. 3,000 classes w/ 10 tests each at that speed is about an hour of test time
Does it help localize errors quickly?
Notice entry point is a new requirement not a new feature
This is a tight loop - short iterations are important
We’re only producing code needed for the current test
The goal is to produce working clean code that fulfills the requirement
Though poorly worded, the thrust of this quote is helpful.
Add a comment for tests we want to circle back to that aren’t happy path (like in video where passing in coordinates that don’t correspond to a valid board position)
Don’t do calculations in your tests b/c it often means you’re recreating a code calculation - you could just be recreating a bug but the test will pass
Reminder - NUnit 2.6.4 - My version of R# won’t work with NUnit 3.x
It’s mostly about design
Confidence in changes
Is documentation by example
Immediate feedback
Gerard Meszaros uses the term Test Double as the generic term for any kind of pretend object used in place of a real object for testing purposes. The name comes from the notion of a Stunt Double in movies. He then defined four particular kinds of double:
Dummy objects are passed around but never actually used. Usually they are just used to fill parameter lists.
Fake objects actually have working implementations, but usually take some shortcut which makes them not suitable for production (an in memory database is a good example).
Stubs provide canned answers to calls made during the test, usually not responding at all to anything outside what's programmed in for the test. Stubs may also record information about calls, such as an email gateway stub that remembers the messages it 'sent', or maybe only how many messages it 'sent'.
Mocks are what we are talking about here: objects pre-programmed with expectations which form a specification of the calls they are expected to receive.
Only mocks insist upon behavior verification.
Suppose you have class called "ledger" a method called "calculate" that uses a "Calculator" to do different types of calculations depending on the arguments passed to "calculate", for example "multiply(x, y)" or "subtract(x, y)".
Now, suppose you want to test what happens when you call ledger.calculate("5 * 7")
The Chicago/State school would have you assert whether the result is 35. The jUnit/nUnit frameworks are generally geared towards doing this.
The London/Interaction school would have you assert whether Calculator.multiply(5,7) got called. The various mocking frameworks are useful for this, and it can be very useful if, for example, you don't have ownership of the "Calculator" object (suppose it is an external component or service that you cannot test directly, but you do know you have to call in a particular way).
This is the base test class. Don’t worry about the syntax of the mock… just understand what it is.
The Chicago/State school would have you assert whether the result is 35. The jUnit/nUnit frameworks are generally geared towards doing this.
The Chicago/State school would have you assert whether the result is 35. The jUnit/nUnit frameworks are generally geared towards doing this.