Summary
The Art of Unit Testing, Second Edition guides you step by step from writing your first simple tests to developing robust test sets that are maintainable, readable, and trustworthy. You'll master the foundational ideas and quickly move to high-value subjects like mocks, stubs, and isolation, including frameworks such as Moq, FakeItEasy, and Typemock Isolator. You'll explore test patterns and organization, working with legacy code, and even "untestable" code. Along the way, you'll learn about integration testing and techniques and tools for testing databases and other technologies.
About this Book
You know you should be unit testing, so why aren't you doing it? If you're new to unit testing, if you find unit testing tedious, or if you're just not getting enough payoff for the effort you put into it, keep reading.
The Art of Unit Testing, Second Edition guides you step by step from writing your first simple unit tests to building complete test sets that are maintainable, readable, and trustworthy. You'll move quickly to more complicated subjects like mocks and stubs, while learning to use isolation (mocking) frameworks like Moq, FakeItEasy, and Typemock Isolator. You'll explore test patterns and organization, refactor code applications, and learn how to test "untestable" code. Along the way, you'll learn about integration testing and techniques for testing with databases.
The examples in the book use C#, but will benefit anyone using a statically typed language such as Java or C++.
Purchase of the print book includes a free eBook in PDF, Kindle, and ePub formats from Manning Publications.
What's Inside
Create readable, maintainable, trustworthy tests
Fakes, stubs, mock objects, and isolation (mocking) frameworks
Simple dependency injection techniques
Refactoring legacy code
About the Author
Roy Osherove has been coding for over 15 years, and he consults and trains teams worldwide on the gentle art of unit testing and test-driven development. His blog is at ArtOfUnitTesting.com.
Table of Contents
PART 1 GETTING STARTED
The basics of unit testing
A first unit test
PART 2 CORE TECHNIQUES
Using stubs to break dependencies
Interaction testing using mock objects
Isolation (mocking) frameworks
Digging deeper into isolation frameworks
PART 3 THE TEST CODE
Test hierarchies and organization
The pillars of good unit tests
PART 4 DESIGN AND PROCESS
Integrating unit testing into the organization
Working with legacy code
Design and testability
Author(s): Roy Osherove
Edition: 2
Publisher: Manning
Year: 2013
Language: English
Pages: 266
Front cover
brief contents
contents
foreword to the second edition
foreword to the first edition
preface
acknowledgments
about this book
What’s new in the second edition
Who should read this book
Roadmap
Code conventions and downloads
Software requirements
Author Online
Other projects by Roy Osherove
about the cover illustration
Part 1—Getting started
1 The basics of unit testing
1.1 Defining unit testing, step by step
1.1.1 The importance of writing good unit tests
1.1.2 We’ve all written unit tests (sort of)
1.2 Properties of a good unit test
1.3 Integration tests
1.3.1 Drawbacks of nonautomated integration tests compared to automated unit tests
1.4 What makes unit tests good
1.5 A simple unit test example
1.6 Test-driven development
1.7 The three core skills of successful TDD
1.8 Summary
2 A first unit test
2.1 Frameworks for unit testing
2.1.1 What unit testing frameworks offer
2.1.2 The xUnit frameworks
2.2 Introducing the LogAn project
2.3 First steps with NUnit
2.3.1 Installing NUnit
2.3.2 Loading up the solution
2.3.3 Using the NUnit attributes in your code
2.4 Writing your first test
2.4.1 The Assert class
2.4.2 Running your first test with NUnit
2.4.3 Adding some positive tests
2.4.4 From red to green: passing the tests
2.4.5 Test code styling
2.5 Refactoring to parameterized tests
2.6 More NUnit attributes
2.6.1 Setup and teardown
2.6.2 Checking for expected exceptions
2.6.3 Ignoring tests
2.6.4 NUnit’s fluent syntax
2.6.5 Setting test categories
2.7 Testing results that are system state changes instead of return values
2.8 Summary
Part 2—Core techniques
3 Using stubs to break dependencies
3.1 Introducing stubs
3.2 Identifying a filesystem dependency in LogAn
3.3 Determining how to easily test LogAnalyzer
3.4 Refactoring your design to be more testable
3.4.1 Extract an interface to allow replacing underlying implementation
3.4.2 Dependency injection: inject a fake implementation into a unit under test
3.4.3 Inject a fake at the constructor level (constructor injection)
3.4.4 Simulating exceptions from fakes
3.4.5 Injecting a fake as a property get or set
3.4.6 Injecting a fake just before a method call
3.5 Variations on refactoring techniques
3.5.1 Using Extract and Override to create fake results
3.6 Overcoming the encapsulation problem
3.6.1 Using internal and [InternalsVisibleTo]
3.6.2 Using the [Conditional] attribute
3.6.3 Using #if and #endif with conditional compilation
3.7 Summary
4 Interaction testing using mock objects
4.1 Value-based vs. state-based vs. interaction testing
4.2 The difference between mocks and stubs
4.3 A simple handwritten mock example
4.4 Using a mock and a stub together
4.5 One mock per test
4.6 Fake chains: stubs that produce mocks or other stubs
4.7 The problems with handwritten mocks and stubs
4.8 Summary
5 Isolation (mocking) frameworks
5.1 Why use isolation frameworks?
5.2 Dynamically creating a fake object
5.2.1 Introducing NSubstitute into your tests
5.2.2 Replacing a handwritten fake object with a dynamic one
5.3 Simulating fake values
5.3.1 A mock, a stub, and a priest walk into a test
5.4 Testing for event-related activities
5.4.1 Testing an event listener
5.4.2 Testing whether an event was triggered
5.5 Current isolation frameworks for .NET
5.6 Advantages and traps of isolation frameworks
5.6.1 Traps to avoid when using isolation frameworks
5.6.2 Unreadable test code
5.6.3 Verifying the wrong things
5.6.4 Having more than one mock per test
5.6.5 Overspecifying the tests
5.7 Summary
6 Digging deeper into isolation frameworks
6.1 Constrained and unconstrained frameworks
6.1.1 Constrained frameworks
6.1.2 Unconstrained frameworks
6.1.3 How profiler-based unconstrained frameworks work
6.2 Values of good isolation frameworks
6.3 Features supporting future-proofing and usability
6.3.1 Recursive fakes
6.3.2 Ignored arguments by default
6.3.3 Wide faking
6.3.4 Nonstrict behavior of fakes
6.3.5 Nonstrict mocks
6.4 Isolation framework design antipatterns
6.4.1 Concept confusion
6.4.2 Record and replay
6.4.3 Sticky behavior
6.4.4 Complex syntax
6.5 Summary
Part 3—The test code
7 Test hierarchies and organization
7.1 Automated builds running automated tests
7.1.1 Anatomy of a build script
7.1.2 Triggering builds and integration
7.2 Mapping out tests based on speed and type
7.2.1 The human factor when separating unit from integration tests
7.2.2 The safe green zone
7.3 Ensuring tests are part of source control
7.4 Mapping test classes to code under test
7.4.1 Mapping tests to projects
7.4.2 Mapping tests to classes
7.4.3 Mapping tests to specific unit of work method entry points
7.5 Cross-cutting concerns injection
7.6 Building a test API for your application
7.6.1 Using test class inheritance patterns
7.6.2 Creating test utility classes and methods
7.6.3 Making your API known to developers
7.7 Summary
8 The pillars of good unit tests
8.1 Writing trustworthy tests
8.1.1 Deciding when to remove or change tests
8.1.2 Avoiding logic in tests
8.1.3 Testing only one concern
8.1.4 Separate unit from integration tests
8.1.5 Assuring code review with code coverage
8.2 Writing maintainable tests
8.2.1 Testing private or protected methods
8.2.2 Removing duplication
8.2.3 Using setup methods in a maintainable manner
8.2.4 Enforcing test isolation
8.2.5 Avoiding multiple asserts on different concerns
8.2.6 Comparing objects
8.2.7 Avoiding overspecification
8.3 Writing readable tests
8.3.1 Naming unit tests
8.3.2 Naming variables
8.3.3 Asserting yourself with meaning
8.3.4 Separating asserts from actions
8.3.5 Setting up and tearing down
8.4 Summary
Part 4—Design and process
9 Integrating unit testing into the organization
9.1 Steps to becoming an agent of change
9.1.1 Be prepared for the tough questions
9.1.2 Convince insiders: champions and blockers
9.1.3 Identify possible entry points
9.2 Ways to succeed
9.2.1 Guerrilla implementation (bottom up)
9.2.2 Convincing management (top down)
9.2.3 Getting an outside champion
9.2.4 Making progress visible
9.2.5 Aiming for specific goals
9.2.6 Realizing that there will be hurdles
9.3 Ways to fail
9.3.1 Lack of a driving force
9.3.2 Lack of political support
9.3.3 Bad implementations and first impressions
9.3.4 Lack of team support
9.4 Influence factors
9.5 Tough questions and answers
9.5.1 How much time will unit testing add to the current process?
9.5.2 Will my QA job be at risk because of unit testing?
9.5.3 How do we know unit tests are actually working?
9.5.4 Is there proof that unit testing helps?
9.5.5 Why is the QA department still finding bugs?
9.5.6 We have lots of code without tests: where do we start?
9.5.7 We work in several languages: is unit testing feasible?
9.5.8 What if we develop a combination of software and hardware?
9.5.9 How can we know we don’t have bugs in our tests?
9.5.10 My debugger shows that my code works; why do I need tests?
9.5.11 Must we do TDD-style coding?
9.6 Summary
10 Working with legacy code
10.1 Where do you start adding tests?
10.2 Choosing a selection strategy
10.2.1 Pros and cons of the easy-first strategy
10.2.2 Pros and cons of the hard-first strategy
10.3 Writing integration tests before refactoring
10.4 Important tools for legacy code unit testing
10.4.1 Isolate dependencies easily with unconstrained isolation frameworks
10.4.2 Use JMockit for Java legacy code
10.4.3 Use Vise while refactoring your Java code
10.4.4 Use acceptance tests before you refactor
10.4.5 Read Michael Feathers’s book on legacy code
10.4.6 Use NDepend to investigate your production code
10.4.7 Use ReSharper to navigate and refactor production code
10.4.8 Detect duplicate code (and bugs) with Simian and TeamCity
10.5 Summary
11 Design and testability
11.1 Why should I care about testability in my design?
11.2 Design goals for testability
11.2.1 Make methods virtual by default
11.2.2 Use interface-based designs
11.2.3 Make classes nonsealed by default
11.2.4 Avoid instantiating concrete classes inside methods with logic
11.2.5 Avoid direct calls to static methods
11.2.6 Avoid constructors and static constructors that do logic
11.2.7 Separate singleton logic from singleton holders
11.3 Pros and cons of designing for testability
11.3.1 Amount of work
11.3.2 Complexity
11.3.3 Exposing sensitive IP
11.3.4 Sometimes you can’t
11.4 Alternatives to designing for testability
11.4.1 Design arguments and dynamically typed languages
11.5 Example of a hard-to-test design
11.6 Summary
11.7 Additional resources
appendix Tools and frameworks
A.1 Isolation frameworks
A.1.1 Moq
A.1.2 Rhino Mocks
A.1.3 Typemock Isolator
A.1.4 JustMock
A.1.5 Microsoft Fakes (Moles)
A.1.6 NSubstitute
A.1.7 FakeItEasy
A.1.8 Foq
A.1.9 Isolator++
A.2 Test frameworks
A.2.1 Mighty Moose (a.k.a. ContinuousTests) continuous runner
A.2.2 NCrunch continuous runner
A.2.3 Typemock Isolator test runner
A.2.4 CodeRush test runner
A.2.5 ReSharper test runner
A.2.6 TestDriven.NET runner
A.2.7 NUnit GUI runner
A.2.8 MSTest runner
A.2.9 Pex
A.3 Test APIs
A.3.1 MSTest API—Microsoft’s unit testing framework
A.3.2 MSTest for Metro Apps (Windows Store)
A.3.3 NUnit API
A.3.4 xUnit.net
A.3.5 Fluent Assertions helper API
A.3.6 Shouldly helper API
A.3.7 SharpTestsEx helper API
A.3.8 AutoFixture helper API
A.4 IoC containers
A.4.1 Autofac
A.4.2 Ninject
A.4.3 Castle Windsor
A.4.4 Microsoft Unity
A.4.5 StructureMap
A.4.6 Microsoft Managed Extensibility Framework
A.5 Database testing
A.5.1 Use integration tests for your data layer
A.5.2 Use TransactionScope to roll back changes to data
A.6 Web testing
A.6.1 Ivonna
A.6.2 Team System web test
A.6.3 Watir
A.6.4 Selenium WebDriver
A.6.5 Coypu
A.6.6 Capybara
A.6.7 JavaScript testing
A.7 UI testing (desktop)
A.8 Thread-related testing
A.8.1 Microsoft CHESS
A.8.2 Osherove.ThreadTester
A.9 Acceptance testing
A.9.1 FitNesse
A.9.2 SpecFlow
A.9.3 Cucumber
A.9.4 TickSpec
A.10 BDD-style API frameworks
index
A
B
C
D
E
F
G
H
I
J
L
M
N
O
P
Q
R
S
T
U
V
W
X
Back cover