SlideShare a Scribd company logo
REFACTORING LEGACY WEB
FORMS FOR TEST AUTOMATION
Author: Stephen Fuqua
Last Revised: December 2014
All content by the author or from public domain sources unless otherwise noted
PART 1
The Challenge
Taking Over Legacy Code
• Given you understand the value of test
automation
• Given you are handed a legacy application to
maintain and enhance
• Given the application is in ASP.Net Web Forms
• When you try to add tests
• Then you find that test-driven development is
literally impossible.
Web Forms Challenge
• Testing ASP.Net Web Forms is problematic:
• Tutorials show poor design, leading many
developers to mix UI, business, and data access logic
into a single class (the code behind).
• ASP.Net functionality such as Session and ViewState
are difficult to manipulate in an automated test.
• Likewise, Web Forms architecture makes it difficult
to access and manipulate form controls in tests.
• Temptation: automate tests on the UI itself.
Test Pyramid
• In Succeeding with Agile, Mike Cohn describes a
pyramid of automated tests:
• Dominated by unit tests, and
• Featuring service (system) tests that functionally
integrate the units, and
• Including just a few UI tests, to confirm that form fields
connect to the services.
UI
Service
Unit
http://www.mountaingoatsoftware.com/blog/
the-forgotten-layer-of-the-test-automation-
pyramid
Right-siding the Pyramid
• UI tests are brittle, expensive to write, and time
consuming, to paraphrase Cohn.
• With judicious refactoring, it is possible to
continue using Web Forms and still achieve the
goals of test automation and test-driven
development
• To overcome this challenge…
• Use the Model-View-Presenter pattern, and
• Introduce test isolation techniques.
PART 2
Toolkit
Model-View-Presenter
• Model-View-Presenter, or MVP, is a specialized
version of Model-View-Controller (MVC).
• Split the traditional code behind into View and
Presenter.
View Presenter Model
Code-
Behind
Business Logic
Test Isolation Flow Chart
http://www.safnet.com/writing/tech/2014/08/unit-test-
isolation-for-legacy-net-code.html
Refactoring
• Start refactoring the code, carefully introducing
isolation techniques in moving to MVP.
• Sprouting – the code behind sprouts into model,
view, and presenter. AKA Extract Method.
• Adapters – write adapters for ASP.Net functionality
that cannot be manipulated in unit tests.
• Stubs & mocks – use interfaces and dependency
injection properly, then apply stubs and mocks in
the new unit test code.
The Straw Man
• To illustrate these techniques, I resurrected the
code for www.ibamonitoring.org.
• It is already split into web project and “back
end” library for business logic and data access.
• Contains unit and integration tests for the
library, but none for the web project.
• Originally used Microsoft Moles (now Fakes) to
isolate some of the code for unit testing.
• The app is sound, but patterns are used
inconsistently.
Site Conditions Page
PART 3
Refactoring to Adapters
Adapters for Caching
• Introduce adapters that wrap Session,
Application, etc.
• Side benefit: centralizes magic strings and
casting from object to appropriate types.
• Use lazy-loading for Property Injection,
combined with Test Specific Subclasses, to
allow production code to access real objects
and tests to access fake objects.
Example: An Adapter for Session
• Original code already
contained this
UserStateManager in
order to centralize
magic strings.
• It has now been
refactored to an
instance class with an
interface that can be
mocked.
Using the Session Adapter
• Adding the lazy-load to a base class.
• Note the use of HttpSessionStateWrapper, which
turns old Session into HttpSessionStateBase.
Unit Testing the Adapter
• Even an adapter can be unit tested… you’ll
need a fake Session for that. One that doesn’t
start the ASP.Net engine.
• In other words, a Test Specific Subclass.
• But Session is sealed.
• Hence the use of HttpSessionStateBase, which
is not sealed!
Unit Testing the Adapter, cont.
PART 4
Refactoring to Model-View-Presenter (MVP)
The MVP Pattern
• Model contains business
logic or accesses business
logic in services.
• View contains properties
and methods for accessing
form controls and changing
the display.
• Presenter connects the two;
all “UI logic” moves from
View to Presenter.
• Use Separation of Interfaces
to facilitate testing the
Presenter.
Deconstructing the View
• Move use of dependencies to the Presenter.
• Create a property for each control that needs
to be accessed by the UI logic in the Presenter.
• And methods for behavior changes that the
Presenter should invoke in the UI.
Using the Presenter
• Add the Presenter to the concrete View.
• The view’s events make calls to the Presenter.
Class Diagrams After Refactor
Discussion
• View’s interface and Presenter are still in the
web project.
• This example does not show behavior changes
in the view.
• This app’s Model is not well-constructed:
• Presenter calls static methods that can’t be mocked.
• Presenter is invoking business logic, not just UI logic
– extract that into the Model.
Evaluating the Presenter
• Green – UI layer
logic
• Red – business
logic
• Yellow – extract
to methods with
validation
• Business logic
should return a
modified SiteVisit
object after
performing
inserts.
• Just noticed –
first line isn’t
used! Remove
GlobalMap from
constructor.
Business Logic – the Facade
• For business logic, I prefer to create a Façade
class that takes just a few arguments and hides
the complexity of data access and
manipulation.
• The Façade itself can be injected into the
Presenter’s constructor.
Refreshing the Class Diagram
SiteConditionsFacade
Refactored Presenter
• Accesses
state.
• Retrieves
and validates
form values.
• Forwards
values to the
business
layer.
• Is fully
testable.
Result
• Original code behind was impossible to test,
netting 40 lines of untested code.
• Now, code behind has:
• Some untested properties – but low risk of defects.
• Event handler with one new line of untested code.
• A new constructor with one line of untested code.
• The presenter, and the wrappers for Session
and Application, are 100% unit testable.
PART 5
Unit Testing the View / Introducing Dependency Injection
Web Forms and Dependency Injection
• Without dependency injection, I cannot test
the View’s constructor or events.
• There is a means available for setting up full
dependency in Web Forms: an HttpModule.
• … and an open source solution to setup Unity
as an Inversion of Control (IoC) container:
https://bitbucket.org/KyleK/unity.webforms
• Likely there are similar packages for other IoC
containers, but Unity is my current tool.
Evaluating the View’s Constructor
• Here is the updated constructor for the View,
injecting the new Façade into the Presenter.
• The presence of the View in the Presenter’s
constructor introduces a circular dependency,
thus preventing use of any IoC container.
• View depends on Presenter depends on View
Solution: Abstract Factory
• A solution to this conundrum is a Factory class
with methods to build the presenters.
• The Factory can wrap the IoC container.
• It can access session and app state from
HttpContext.Current.
• In order to unit test the factory, we’ll want to access
state variables through lazy-loaded properties.
• Be sure to keep the abstract factory in the web
project. Discussion: http://odetocode.com/Articles/112.aspx
PresenterFactory
Injecting View and State
• To set the instance-specific view and state
values resolving the presenter type, use the
ResolverOverride parameter argument.
Setup Dependency Injection
• The installed package created class
UnityWebFormsStart in the web project – add
dependencies to this class.
• Use Registration by Convention to auto-map
the classes in the web project and library.
Modify The View
• Add the Factory as a
constructor argument
in the view / code
behind file.
• Call the Factory to
create the Presenter.
Unit Testing the View
• We should be able to unit test the view’s
constructor quite easily now
• What about the event that calls the presenter?
It has two more dependencies to isolate:
• Page.IsValid – create adapter an lazy load.
• Response.Redirect – lazy load an instance of
HttpResponseBase, and create a TSS class.
• Best to move these calls to the Presenter –
skipping that for time’s sake right now.
PageAdapter
• Three commonly used
properties to start with, can
be expanded as needed.
• Temporarily violating YAGNI
principle, but it is a trivial and
likely useful violation.
• Page is not sealed and thus
could be sub-classed for
testing, but it simply isn’t
worth it for 4 lines of code.
Code Coverage
• The entire web project is up to 7% coverage.
• 25% uncovered in the Factory from lazy loading.
• GlobalMap and UserStateManager are legacy –
they can be tested now, but are not fully yet.
• The View has 5.5% coverage, Presenter 100%.
PART 6
Interlude – Toward MVC
• The goal for this project is to automate tests,
not to migrate the framework, but…
• … in retrospection, the Presenter we’ve
developed is definitely starting to look like a
Controller from an ASP.Net MVC project.
• The next step in test automation is to address
service level testing – and that will be made
cleaner by refactoring the Presenter to be very
close to an MVC Controller.
Refactoring the Presenter Interface
• A Controller has direct access to HttpContext:
• The Presenter is in the web project - we can use
HttpContext.Current to access these values.
• Controller actions accept form data, either as a
set of variables or using model binding.
• Use the View as the “model” (View Model) and pass
to the action instead of to the constructor.
• Validation should stay with the View Model.
Gap Analysis
Session Response
Application Request
• The lazy-loaded adapters
were previously in a base
class for ASPX pages.
• Move to a base class for
Presenters.
• Leave out PageAdapters
because those values
belong in the View /
ViewModel.
Lazy Loading Adapters
• Normally an MVC view model would be a
concrete class, not an interface.
• In this case, convenient to leave as an interface
– if changing from Web Forms to MVC, then it
will be trivial to change the interface to a
concrete POCO.
• Updated signatures:
Convert to ViewModel
• The project uses validation controls in the ASPX
file - need to rely on Page.IsValid for validation.
• For test automation, best to have the Presenter
react to validation problems
• Create an IsValid property in the view interface,
and utilize it in the presenter.
• Limitation – can’t test the validation details,
only the Presenter’s response to invalid data.
• Might not need PageAdapter at all now…
View Model Validation
• The code behind in the View has become much
simpler – call the factory, then call the
SaveConditions “action”, passing the View itself
as the View Model.
• What about the exception handling? In this
case, it is ASPX specific and I will leave it alone.
Updated View
PART 6
Service Level Testing with SpecFlow
• SpecFlow is a Visual Studio extension for
writing business requirements / acceptance
tests using the Gherkin language.
• Using SpecFlow, we can add service-level tests
that connect to the Presenter classes.
• … and, when we’re ready to enhance the
application, we can write new acceptance tests
in a Behavior Driven Development mode.
SpecFlow
• As an IBA observer, I want to
record the conditions for a
site visit so that I can submit
point count observations.
• Try entering realistic data in all
the fields – expect to go to the
Point count page.
• Try using end time less than
start time – expect error.
• Try leaving the form blank –
expect error.
User Story and Brief Confirmations
• Assuming SpecFlow is installed, and you have a
test project configured for MSTest*...
• Create a Feature file called SiteConditions.
• Modify the user story and scenario name.
• I will remove the tag and customize the steps in
the following slides.
Add a Feature
* Or leave with NUnit if you prefer
• Since this test is driving
a UI, input values
include the available
options for dropdowns
controls.
• We could initialize these
through a Test Hook, or
make the initialization
transparent by including
them the test definition.
Happy Path – Setup Dropdowns
• Fill in valid values.
• Simulate pressing the
Next button.
• Confirm the expected
page redirect.
• And the unstated
expectation that the
submitted data will be
saved into a database.
Happy Path – Fill in Form, Submit It
• Run the scenarios…
• Not surprisingly, the scenarios fails to run:
there is no connection between the scenario
and our application code.
Run the Scenario
• Need to right-click and choose Generate Step
Definitions.
• Creates a step definition file that provides a
template for linking the data and actions to the
application code.
Generate Step Definitions
• The metadata values need to be inserted into
the database – which brings us to…
• As with any database-integrated tests, you’ll
want to use a sandbox database. I will use the
same LocalDB instance that I already created
for stored procedure testing.
• Make sure the test project’s app.config file is
properly setup for data access.
• Use an ORM for quick-and-easy backdoor data
access (showing OrmLite here).
Sandbox Database
• Before the
complete test
run: setup
database
access.
• Before each
individual
test: clear out
all of the
tables that
will be used.
Test Hooks
• Now, edit the
generated step
definition
template, reading
the data from
SpecFlow and
writing into the
database.
• For convenience,
cache a local copy
of the objects and
their Id values.
Insert the Metadata
Setup the View Model
• The step “I have entered these values into the
form” contains the View Model / form data.
• Create a stub implementation of the View, and
populate it using SpecFlow’s Assist Helpers.
• Store the view model in a static variable for use
in the Given step.
• In order to call the Presenter without using
ASP.Net, create stub implementations of
IUserStateManager and HttpResponseBase.
• Instantiate the Presenter using Unity and inject
the stub objects.
Call the Presenter
• First validate the redirect.
• Then use OrmLite again to validate that the
actual data stored in the database matches the
View Model.
Evaluate the Results
• Now we have a fully automated regression test
of the “happy path” scenario for saving site
conditions – using the entire system except for
the ASPX page itself.
• Each additional confirmation can be written as
a new scenario in the same feature file.
• When you re-use a Given, When, or Then
phrase, you will have instant C# code re-use.
• Note that the feature file is essentially a
business requirements document.
Service Test Wrap-Up
CONCLUSION
Keys to Success
• Split code behind into Model-View-Presenter.
• Introduced adapters for ASP.Net classes.
• Session
• Application
• Response
• Introduced interfaces and a Factory class.
• Added Unity for Web Forms to achieve DI.
• Utilized SpecFlow for service-level tests.
• The entire solution is available under the MIT
license, hosted on GitHub.
• Files of particular interest:
• SiteConditions.aspx.cs
• SiteConditionsPresenter.cs
• PresenterFactory.cs
• UserState.cs
• SiteConditionsFacade.cs
• SiteConditions.feature
• SiteConditionsSteps.cs
• TestHooks.cs
Source Code
Preview: Moving to MVC
• Should be able to do something like this…
1. Create an MVC project.
2. Run the original application. Save the generated web
pages as .cshtml pages.
3. Change the Id values, e.g.
ctl00_contentBody_SiteVisitedInput to SiteVisitedInput
(find and replace “ctl00_contentBody” with “”).
4. Move the Presenters to MVC and rename as XyzController.
5. Change View interfaces to concrete ViewModel classes.
6. Update the validation, e.g. with Fluent Validator.

More Related Content

PPTX
Principles and patterns for test driven development
PDF
Unit Test + Functional Programming = Love
PPTX
Unit tests and TDD
PDF
Unit testing - A&BP CC
PPTX
Presentation
PDF
Unit testing, principles
PPT
Integration testing
PPTX
Benefit From Unit Testing In The Real World
Principles and patterns for test driven development
Unit Test + Functional Programming = Love
Unit tests and TDD
Unit testing - A&BP CC
Presentation
Unit testing, principles
Integration testing
Benefit From Unit Testing In The Real World

What's hot (20)

PDF
Unit Tesing in iOS
PPTX
Unit Testing Concepts and Best Practices
PPTX
Best practices unit testing
PPTX
Testing the Untestable
PDF
Unit Testing 101
PPTX
Unit & integration testing
PPTX
An Introduction to Unit Testing
PDF
Unit Testing Fundamentals
PDF
How to Build Your Own Test Automation Framework?
PDF
Unit testing (workshop)
PDF
Workshop unit test
PPTX
Apex Testing and Best Practices
PDF
Unit testing in Force.com platform
PPTX
More on Fitnesse and Continuous Integration (Silicon Valley code camp 2012)
PDF
Unit Testing Done Right
PPT
Unit testing php-unit - phing - selenium_v2
PDF
Clean Unit Test Patterns
PDF
Writing good unit test
PPTX
Java Unit Test and Coverage Introduction
PPTX
Testing Rapidly Changing Applications With Self-Testing Object-Oriented Selen...
Unit Tesing in iOS
Unit Testing Concepts and Best Practices
Best practices unit testing
Testing the Untestable
Unit Testing 101
Unit & integration testing
An Introduction to Unit Testing
Unit Testing Fundamentals
How to Build Your Own Test Automation Framework?
Unit testing (workshop)
Workshop unit test
Apex Testing and Best Practices
Unit testing in Force.com platform
More on Fitnesse and Continuous Integration (Silicon Valley code camp 2012)
Unit Testing Done Right
Unit testing php-unit - phing - selenium_v2
Clean Unit Test Patterns
Writing good unit test
Java Unit Test and Coverage Introduction
Testing Rapidly Changing Applications With Self-Testing Object-Oriented Selen...
Ad

Similar to Refactoring Legacy Web Forms for Test Automation (20)

PPTX
Ria 04 & 05 - First ASP.NET MVC project
PPT
TDD with ASP.NET MVC 1.0
PDF
Building richwebapplicationsusingasp
PPTX
ASP .NET MVC Introduction & Guidelines
PPTX
Out of box page object design pattern, java
PPTX
Mvc presentation
PPTX
Modern ASP.NET Webskills
PPTX
Asp.Net MVC Intro
PPTX
Web forms and automated tests
PDF
learn mvc project in 7 day
PPTX
Writing Testable Code in SharePoint
PPTX
Beyond pageobjects
PPTX
Automated Testing Of EPiServer CMS Sites
PPT
Why do complex software application projects drag?
PPTX
ASP.NET MVC 5 - EF 6 - VS2015
PDF
Introduction to ASP.NET MVC
PPTX
Refactoring For Testability
PPTX
Improve your Web Development using Visual Studio 2010
PDF
Hands on Exploration of Page Objects and Abstraction Layers with Selenium Web...
PDF
Mastering UI automation at Scale: Key Lessons and Best Practices (By Fernando...
Ria 04 & 05 - First ASP.NET MVC project
TDD with ASP.NET MVC 1.0
Building richwebapplicationsusingasp
ASP .NET MVC Introduction & Guidelines
Out of box page object design pattern, java
Mvc presentation
Modern ASP.NET Webskills
Asp.Net MVC Intro
Web forms and automated tests
learn mvc project in 7 day
Writing Testable Code in SharePoint
Beyond pageobjects
Automated Testing Of EPiServer CMS Sites
Why do complex software application projects drag?
ASP.NET MVC 5 - EF 6 - VS2015
Introduction to ASP.NET MVC
Refactoring For Testability
Improve your Web Development using Visual Studio 2010
Hands on Exploration of Page Objects and Abstraction Layers with Selenium Web...
Mastering UI automation at Scale: Key Lessons and Best Practices (By Fernando...
Ad

Recently uploaded (20)

PDF
Which alternative to Crystal Reports is best for small or large businesses.pdf
PPTX
Agentic AI Use Case- Contract Lifecycle Management (CLM).pptx
PDF
Navsoft: AI-Powered Business Solutions & Custom Software Development
PPTX
VVF-Customer-Presentation2025-Ver1.9.pptx
PDF
top salesforce developer skills in 2025.pdf
PDF
Adobe Illustrator 28.6 Crack My Vision of Vector Design
PDF
Upgrade and Innovation Strategies for SAP ERP Customers
PDF
Wondershare Filmora 15 Crack With Activation Key [2025
PPTX
ManageIQ - Sprint 268 Review - Slide Deck
PDF
Odoo Companies in India – Driving Business Transformation.pdf
PPTX
CHAPTER 2 - PM Management and IT Context
PDF
SAP S4 Hana Brochure 3 (PTS SYSTEMS AND SOLUTIONS)
PPT
Introduction Database Management System for Course Database
PPTX
Online Work Permit System for Fast Permit Processing
PDF
Design an Analysis of Algorithms II-SECS-1021-03
PDF
How to Migrate SBCGlobal Email to Yahoo Easily
PPTX
Oracle E-Business Suite: A Comprehensive Guide for Modern Enterprises
PDF
AI in Product Development-omnex systems
PDF
Flood Susceptibility Mapping Using Image-Based 2D-CNN Deep Learnin. Overview ...
PDF
Understanding Forklifts - TECH EHS Solution
Which alternative to Crystal Reports is best for small or large businesses.pdf
Agentic AI Use Case- Contract Lifecycle Management (CLM).pptx
Navsoft: AI-Powered Business Solutions & Custom Software Development
VVF-Customer-Presentation2025-Ver1.9.pptx
top salesforce developer skills in 2025.pdf
Adobe Illustrator 28.6 Crack My Vision of Vector Design
Upgrade and Innovation Strategies for SAP ERP Customers
Wondershare Filmora 15 Crack With Activation Key [2025
ManageIQ - Sprint 268 Review - Slide Deck
Odoo Companies in India – Driving Business Transformation.pdf
CHAPTER 2 - PM Management and IT Context
SAP S4 Hana Brochure 3 (PTS SYSTEMS AND SOLUTIONS)
Introduction Database Management System for Course Database
Online Work Permit System for Fast Permit Processing
Design an Analysis of Algorithms II-SECS-1021-03
How to Migrate SBCGlobal Email to Yahoo Easily
Oracle E-Business Suite: A Comprehensive Guide for Modern Enterprises
AI in Product Development-omnex systems
Flood Susceptibility Mapping Using Image-Based 2D-CNN Deep Learnin. Overview ...
Understanding Forklifts - TECH EHS Solution

Refactoring Legacy Web Forms for Test Automation

  • 1. REFACTORING LEGACY WEB FORMS FOR TEST AUTOMATION Author: Stephen Fuqua Last Revised: December 2014 All content by the author or from public domain sources unless otherwise noted
  • 3. Taking Over Legacy Code • Given you understand the value of test automation • Given you are handed a legacy application to maintain and enhance • Given the application is in ASP.Net Web Forms • When you try to add tests • Then you find that test-driven development is literally impossible.
  • 4. Web Forms Challenge • Testing ASP.Net Web Forms is problematic: • Tutorials show poor design, leading many developers to mix UI, business, and data access logic into a single class (the code behind). • ASP.Net functionality such as Session and ViewState are difficult to manipulate in an automated test. • Likewise, Web Forms architecture makes it difficult to access and manipulate form controls in tests. • Temptation: automate tests on the UI itself.
  • 5. Test Pyramid • In Succeeding with Agile, Mike Cohn describes a pyramid of automated tests: • Dominated by unit tests, and • Featuring service (system) tests that functionally integrate the units, and • Including just a few UI tests, to confirm that form fields connect to the services. UI Service Unit http://www.mountaingoatsoftware.com/blog/ the-forgotten-layer-of-the-test-automation- pyramid
  • 6. Right-siding the Pyramid • UI tests are brittle, expensive to write, and time consuming, to paraphrase Cohn. • With judicious refactoring, it is possible to continue using Web Forms and still achieve the goals of test automation and test-driven development • To overcome this challenge… • Use the Model-View-Presenter pattern, and • Introduce test isolation techniques.
  • 8. Model-View-Presenter • Model-View-Presenter, or MVP, is a specialized version of Model-View-Controller (MVC). • Split the traditional code behind into View and Presenter. View Presenter Model Code- Behind Business Logic
  • 9. Test Isolation Flow Chart http://www.safnet.com/writing/tech/2014/08/unit-test- isolation-for-legacy-net-code.html
  • 10. Refactoring • Start refactoring the code, carefully introducing isolation techniques in moving to MVP. • Sprouting – the code behind sprouts into model, view, and presenter. AKA Extract Method. • Adapters – write adapters for ASP.Net functionality that cannot be manipulated in unit tests. • Stubs & mocks – use interfaces and dependency injection properly, then apply stubs and mocks in the new unit test code.
  • 11. The Straw Man • To illustrate these techniques, I resurrected the code for www.ibamonitoring.org. • It is already split into web project and “back end” library for business logic and data access. • Contains unit and integration tests for the library, but none for the web project. • Originally used Microsoft Moles (now Fakes) to isolate some of the code for unit testing. • The app is sound, but patterns are used inconsistently.
  • 14. Adapters for Caching • Introduce adapters that wrap Session, Application, etc. • Side benefit: centralizes magic strings and casting from object to appropriate types. • Use lazy-loading for Property Injection, combined with Test Specific Subclasses, to allow production code to access real objects and tests to access fake objects.
  • 15. Example: An Adapter for Session • Original code already contained this UserStateManager in order to centralize magic strings. • It has now been refactored to an instance class with an interface that can be mocked.
  • 16. Using the Session Adapter • Adding the lazy-load to a base class. • Note the use of HttpSessionStateWrapper, which turns old Session into HttpSessionStateBase.
  • 17. Unit Testing the Adapter • Even an adapter can be unit tested… you’ll need a fake Session for that. One that doesn’t start the ASP.Net engine. • In other words, a Test Specific Subclass. • But Session is sealed. • Hence the use of HttpSessionStateBase, which is not sealed!
  • 18. Unit Testing the Adapter, cont.
  • 19. PART 4 Refactoring to Model-View-Presenter (MVP)
  • 20. The MVP Pattern • Model contains business logic or accesses business logic in services. • View contains properties and methods for accessing form controls and changing the display. • Presenter connects the two; all “UI logic” moves from View to Presenter. • Use Separation of Interfaces to facilitate testing the Presenter.
  • 21. Deconstructing the View • Move use of dependencies to the Presenter. • Create a property for each control that needs to be accessed by the UI logic in the Presenter. • And methods for behavior changes that the Presenter should invoke in the UI.
  • 22. Using the Presenter • Add the Presenter to the concrete View. • The view’s events make calls to the Presenter.
  • 24. Discussion • View’s interface and Presenter are still in the web project. • This example does not show behavior changes in the view. • This app’s Model is not well-constructed: • Presenter calls static methods that can’t be mocked. • Presenter is invoking business logic, not just UI logic – extract that into the Model.
  • 25. Evaluating the Presenter • Green – UI layer logic • Red – business logic • Yellow – extract to methods with validation • Business logic should return a modified SiteVisit object after performing inserts. • Just noticed – first line isn’t used! Remove GlobalMap from constructor.
  • 26. Business Logic – the Facade • For business logic, I prefer to create a Façade class that takes just a few arguments and hides the complexity of data access and manipulation. • The Façade itself can be injected into the Presenter’s constructor.
  • 29. Refactored Presenter • Accesses state. • Retrieves and validates form values. • Forwards values to the business layer. • Is fully testable.
  • 30. Result • Original code behind was impossible to test, netting 40 lines of untested code. • Now, code behind has: • Some untested properties – but low risk of defects. • Event handler with one new line of untested code. • A new constructor with one line of untested code. • The presenter, and the wrappers for Session and Application, are 100% unit testable.
  • 31. PART 5 Unit Testing the View / Introducing Dependency Injection
  • 32. Web Forms and Dependency Injection • Without dependency injection, I cannot test the View’s constructor or events. • There is a means available for setting up full dependency in Web Forms: an HttpModule. • … and an open source solution to setup Unity as an Inversion of Control (IoC) container: https://bitbucket.org/KyleK/unity.webforms • Likely there are similar packages for other IoC containers, but Unity is my current tool.
  • 33. Evaluating the View’s Constructor • Here is the updated constructor for the View, injecting the new Façade into the Presenter. • The presence of the View in the Presenter’s constructor introduces a circular dependency, thus preventing use of any IoC container. • View depends on Presenter depends on View
  • 34. Solution: Abstract Factory • A solution to this conundrum is a Factory class with methods to build the presenters. • The Factory can wrap the IoC container. • It can access session and app state from HttpContext.Current. • In order to unit test the factory, we’ll want to access state variables through lazy-loaded properties. • Be sure to keep the abstract factory in the web project. Discussion: http://odetocode.com/Articles/112.aspx
  • 36. Injecting View and State • To set the instance-specific view and state values resolving the presenter type, use the ResolverOverride parameter argument.
  • 37. Setup Dependency Injection • The installed package created class UnityWebFormsStart in the web project – add dependencies to this class. • Use Registration by Convention to auto-map the classes in the web project and library.
  • 38. Modify The View • Add the Factory as a constructor argument in the view / code behind file. • Call the Factory to create the Presenter.
  • 39. Unit Testing the View • We should be able to unit test the view’s constructor quite easily now • What about the event that calls the presenter? It has two more dependencies to isolate: • Page.IsValid – create adapter an lazy load. • Response.Redirect – lazy load an instance of HttpResponseBase, and create a TSS class. • Best to move these calls to the Presenter – skipping that for time’s sake right now.
  • 40. PageAdapter • Three commonly used properties to start with, can be expanded as needed. • Temporarily violating YAGNI principle, but it is a trivial and likely useful violation. • Page is not sealed and thus could be sub-classed for testing, but it simply isn’t worth it for 4 lines of code.
  • 41. Code Coverage • The entire web project is up to 7% coverage. • 25% uncovered in the Factory from lazy loading. • GlobalMap and UserStateManager are legacy – they can be tested now, but are not fully yet. • The View has 5.5% coverage, Presenter 100%.
  • 42. PART 6 Interlude – Toward MVC
  • 43. • The goal for this project is to automate tests, not to migrate the framework, but… • … in retrospection, the Presenter we’ve developed is definitely starting to look like a Controller from an ASP.Net MVC project. • The next step in test automation is to address service level testing – and that will be made cleaner by refactoring the Presenter to be very close to an MVC Controller. Refactoring the Presenter Interface
  • 44. • A Controller has direct access to HttpContext: • The Presenter is in the web project - we can use HttpContext.Current to access these values. • Controller actions accept form data, either as a set of variables or using model binding. • Use the View as the “model” (View Model) and pass to the action instead of to the constructor. • Validation should stay with the View Model. Gap Analysis Session Response Application Request
  • 45. • The lazy-loaded adapters were previously in a base class for ASPX pages. • Move to a base class for Presenters. • Leave out PageAdapters because those values belong in the View / ViewModel. Lazy Loading Adapters
  • 46. • Normally an MVC view model would be a concrete class, not an interface. • In this case, convenient to leave as an interface – if changing from Web Forms to MVC, then it will be trivial to change the interface to a concrete POCO. • Updated signatures: Convert to ViewModel
  • 47. • The project uses validation controls in the ASPX file - need to rely on Page.IsValid for validation. • For test automation, best to have the Presenter react to validation problems • Create an IsValid property in the view interface, and utilize it in the presenter. • Limitation – can’t test the validation details, only the Presenter’s response to invalid data. • Might not need PageAdapter at all now… View Model Validation
  • 48. • The code behind in the View has become much simpler – call the factory, then call the SaveConditions “action”, passing the View itself as the View Model. • What about the exception handling? In this case, it is ASPX specific and I will leave it alone. Updated View
  • 49. PART 6 Service Level Testing with SpecFlow
  • 50. • SpecFlow is a Visual Studio extension for writing business requirements / acceptance tests using the Gherkin language. • Using SpecFlow, we can add service-level tests that connect to the Presenter classes. • … and, when we’re ready to enhance the application, we can write new acceptance tests in a Behavior Driven Development mode. SpecFlow
  • 51. • As an IBA observer, I want to record the conditions for a site visit so that I can submit point count observations. • Try entering realistic data in all the fields – expect to go to the Point count page. • Try using end time less than start time – expect error. • Try leaving the form blank – expect error. User Story and Brief Confirmations
  • 52. • Assuming SpecFlow is installed, and you have a test project configured for MSTest*... • Create a Feature file called SiteConditions. • Modify the user story and scenario name. • I will remove the tag and customize the steps in the following slides. Add a Feature * Or leave with NUnit if you prefer
  • 53. • Since this test is driving a UI, input values include the available options for dropdowns controls. • We could initialize these through a Test Hook, or make the initialization transparent by including them the test definition. Happy Path – Setup Dropdowns
  • 54. • Fill in valid values. • Simulate pressing the Next button. • Confirm the expected page redirect. • And the unstated expectation that the submitted data will be saved into a database. Happy Path – Fill in Form, Submit It
  • 55. • Run the scenarios… • Not surprisingly, the scenarios fails to run: there is no connection between the scenario and our application code. Run the Scenario
  • 56. • Need to right-click and choose Generate Step Definitions. • Creates a step definition file that provides a template for linking the data and actions to the application code. Generate Step Definitions
  • 57. • The metadata values need to be inserted into the database – which brings us to… • As with any database-integrated tests, you’ll want to use a sandbox database. I will use the same LocalDB instance that I already created for stored procedure testing. • Make sure the test project’s app.config file is properly setup for data access. • Use an ORM for quick-and-easy backdoor data access (showing OrmLite here). Sandbox Database
  • 58. • Before the complete test run: setup database access. • Before each individual test: clear out all of the tables that will be used. Test Hooks
  • 59. • Now, edit the generated step definition template, reading the data from SpecFlow and writing into the database. • For convenience, cache a local copy of the objects and their Id values. Insert the Metadata
  • 60. Setup the View Model • The step “I have entered these values into the form” contains the View Model / form data. • Create a stub implementation of the View, and populate it using SpecFlow’s Assist Helpers. • Store the view model in a static variable for use in the Given step.
  • 61. • In order to call the Presenter without using ASP.Net, create stub implementations of IUserStateManager and HttpResponseBase. • Instantiate the Presenter using Unity and inject the stub objects. Call the Presenter
  • 62. • First validate the redirect. • Then use OrmLite again to validate that the actual data stored in the database matches the View Model. Evaluate the Results
  • 63. • Now we have a fully automated regression test of the “happy path” scenario for saving site conditions – using the entire system except for the ASPX page itself. • Each additional confirmation can be written as a new scenario in the same feature file. • When you re-use a Given, When, or Then phrase, you will have instant C# code re-use. • Note that the feature file is essentially a business requirements document. Service Test Wrap-Up
  • 65. Keys to Success • Split code behind into Model-View-Presenter. • Introduced adapters for ASP.Net classes. • Session • Application • Response • Introduced interfaces and a Factory class. • Added Unity for Web Forms to achieve DI. • Utilized SpecFlow for service-level tests.
  • 66. • The entire solution is available under the MIT license, hosted on GitHub. • Files of particular interest: • SiteConditions.aspx.cs • SiteConditionsPresenter.cs • PresenterFactory.cs • UserState.cs • SiteConditionsFacade.cs • SiteConditions.feature • SiteConditionsSteps.cs • TestHooks.cs Source Code
  • 67. Preview: Moving to MVC • Should be able to do something like this… 1. Create an MVC project. 2. Run the original application. Save the generated web pages as .cshtml pages. 3. Change the Id values, e.g. ctl00_contentBody_SiteVisitedInput to SiteVisitedInput (find and replace “ctl00_contentBody” with “”). 4. Move the Presenters to MVC and rename as XyzController. 5. Change View interfaces to concrete ViewModel classes. 6. Update the validation, e.g. with Fluent Validator.