Testing and MXUnit in ColdFusion

advertisement
Testing and MXUnit
In ColdFusion
By Denard Springle
July 2011, NVCFUG
• What is MXUnit?
•
A testing framework for use in testing components (cfc’s)
One piece of an integrated development methodology (e.g.
Hudson, Ant and MXUnit)
One tool in the Test Driven developers toolkit
One tool in the Object-Oriented developers toolkit
•
It is not useful for testing non-component templates or output
•
•
•
•
HTML
•
Javascript/Ajax
•
CSS
•
Etc.
• Test Environment
•
•
A well organized SDLC environment includes:
•
One or more development servers where code is developed and
debugged by developers.
•
One or more testing servers where code is tested using MXUnit (and/or
other test frameworks), and is used by QA for regression testing.
•
One or more production servers where fully tested and certified code is
released.
Dedicated testing servers prevent:
•
Developers from crashing the test server
•
Production being used to test code which may
leave datasources in an unknown state
•
Writing complex extended DAO testing frameworks
• Installing MXUnit
•
•
•
Download the latest MXUnit
Framework.
Unzip into webroot
Test the install:
http://myserver/mxunit/index.cfm
•
Webroot is the root directory of the
website you are testing.
• Anatomy of a Test Case
Part One
•
•
•
•
•
The TestCase file is a component (a .cfc file)
The filename either starts or ends with "Test"
The component extends mxunit.framework.TestCase or extends
a component that eventually extends
mxunit.framework.TestCase
The TestCase can contain setUp() and tearDown() functions that
will run prior to and after each and every test
The TestCase can contain beforeTests() and afterTests()
functions that will be run once before and once
after all tests
• Anatomy of a Test Case
Part Two
•
•
•
•
The TestCase can contain any number of public methods. Those
public methods are considered tests regardless of their name,
and MXUnit will run them as tests.
Any private methods are not considered tests and will not be
run by MXUnit
Failures will be recorded as failures; errors as errors; and
successes as successes
Inside of your tests, you make assertions on the
results of functions that you call on your component
under test
• Anatomy of a Test Case
Part Three
•
To very quickly get started, you can run the test by loading it in
the browser and suffixing it with "?method=runTestRemote",
like so:
http://localhost/tests/MyTest.cfc?method=runTestRemote
•
Example TestCase:
• Test Driven Development (TDD)
By Kent Beck
1.
2.
3.
4.


Write the test first
Watch the test fail
Write the component
Watch the test pass
Test Driven Development allows you to write your test cases
before you begin development.
One caveat to TDD is the components you develop
are only as good as the tests you write. So get
good at writing tests if you want to use TDD.
• Assertions
as·sert - to demonstrate the existence of
•
The core of unit testing, i.e. Assertions, are simple:
•
•
You assert something, and if that assertion does not result in your
expectation, the test fails.
•
All code AFTER a failed assertion will NOT execute.
•
Repeat: A failed assertion means the end of that test.
This is why it's best practice to not load each test function with lots of
assertions.
•
•
A single failed assertion means that any subsequent
assertions in a test will not execute and thus you won't
know if those assertions indicate further problems
in your code.
In other words, one assertion per test function is
strongly suggested
• Built-In Assertions
•
assertTrue(boolean condition [,String message])
•
•
assertEquals (expected,actual [, String message])
•
•
assertTrue is one of the two "bread and butter" assertions in any testing
framework. The philosophy is simple: you assert something to be true;
if it's not, the test fails
assertEquals is the other core assertion. Here, you're asserting that
some actual result equals some expected result. This could
be two numbers, two strings, two structs, whatever.
For now, MXUnit follows the JUnit pattern of using a
single assertEquals to compare any type of data.
assertFalse(boolean condition [, String message])
• Built-In Assertions
•
assertSame (obj1,obj2 [,String message])
•
•
Used to determine if the obj1 and obj2 refer to the same instance.
Note that arrays in Adobe ColdFusion are passed by value, so, this will
always fail for arrays.
assertNotSame (obj1,obj2 [,String message])
•
Used to determine if the obj1 and obj2 do not refer to the same
instance.
Note that arrays in Adobe ColdFusion are passed by value,
so, this will always pass for arrays.
• MXUnit Assertion Extensions
These are extensions to the base set of assertions and are specific to ColdFusion.
•
assertIsTypeOf (component obj, String type)
•
•
assertIsXMLDoc (any xml [, String message])
•
•
Determines if xml is a valid XML DOM object.
assertIsArray (any obj1)
•
•
Determines if obj is of type. type needs to be fully qualified (i.e.
‘core.beans.User’).
Determines if the obj1 is a valid array.
assertIsDefined*(any obj1)
•
Determines if the obj1 has been defined in the
available scope.
• MXUnit Assertion Extensions
•
assertIsEmpty (any obj1)
•
•
assertIsEmptyArray (any obj1,[String message])
•
•
Determines if the obj1 is an array with no elements.
assertIsEmptyQuery(any obj1,[String message])
•
•
Determines if the obj1 is a 0 length string or NULL equivalent.
Determines if the obj1 is a query with no rows.
assertIsEmptyStruct (any obj1,[String message])
•
Determines if the obj1 is a struct with no keys or
values.
• MXUnit Assertion Extensions
•
assertIsQuery (any q)
•
•
Determines if q is a valid ColdFusion query.
assertIsStruct (any obj)
•
Determines if obj is a valid ColdFusion structure.
• Provides
Custom
Assertions
an easy way to add custom assertions to your tests without having to change the mxunit core.
•
•
The steps for creating your custom assertion are as follows:
1.
Write a test for your assertion
2.
Write the assertion
3.
Decide how you want to load it: Always or only on selected tests.
Assertion Rules:
•
Your assertion will need to throw mxunit.exception.AssertionFailedError
or use an existing assertion that throws this exception.
•
If you want to have optional first or last parameter
message, you will need to call
normalizeArguments(arguments) in your code.
• Data Driven Testing
With MXUnit dataproviders
•
Data driven testing allows you to execute tests with a wide variety of
input data. This can make creating and executing certain kinds of
tests efficient and very powerful. Essentially, you provide a reference
to a collection of data and MXUnit will iterate over that data and
execute the test for each item in the collection.
•
Data Providers
•
•
Array
•
Query
•
List
•
Excel/CSV
•
Iterator
mxunit:dataprovider=“<data provider>”
• DAO Test Adapter Pattern
A Test Adapter Pattern for DAO Testing
•
Transactions
•
The goal is to have a way to execute code that utilizes database logic
(inserts, updates, deletes, etc.), test the results of the code, and set the
state of the database back to a known state.
•
Leveraging transactions is one way to achieve this.
•
The process is to execute code and after assertions, roll back any
transactions. Sounds simple ... it is and it isn't.
•
ColdFusion is limited with transaction handling - you cannot
have nested transactions and you need to use
the <cftransaction ...> tag.
• DAO Test Adapter Pattern
A Test Adapter Pattern for DAO Testing
•
•
•
•
•
•
Write the test first
Extend the DAO component under test to DAOTestAdapter, or
similar
Refactor our DAO component: extracting the database logic to a
package access method with no transaction handling
Code this packageaccessed method within the transaction block
of the public DAO method
Create a method in DAOTestAdapter that overrides
the DAO method under test and calls the package
access method, but rolls back the transaction
no matter what
Run the test. The expected behavior is that data is
inserted into the db, and the method returns true
• Assertion Patterns
**The terms here are taken from the outstanding book "Test-Driven" by Lasse Koskela.
•
Resulting-State Assertion
•
•
The resulting-state assertion tests data. It says "I'm doing something to
my object that will change that object's data, or 'state'. I'm going to test
that the resulting state of my object is as I expect". A simple example is
the common "bank account" or "transaction" example: You have two
accounts, you transfer $20 from one account to another, and you test
that the first account is 20 bucks shorter and the second account has
that 20 bucks.
Different instances, same data
•
The different-instances, same-data pattern is common
in DAO testing. Essentially, we're asserting that two
objects are different instances but contain the same
data. In MXUnit, you can test for "different instance"
by using the assertNotSame() assertion.
• Assertion Patterns
•
Guard Assertion
•
The guard assertion is simply a slight variant on the resulting state assertion;
typically, the difference is that toward the top of the test, before you get into the
"guts" of your assertion(s), you check the object for some condition that you want to
ensure exists before proceeding with the meat of your tests. Think of it as "If this
condition isn't true, I want to fail right now because the rest of the tests don't
matter". Usually the "guard" is just a simple assertion for equality, often to check
that a "default" condition exists.
•
Interaction Assertion
•
With interaction assertions, we're testing to make sure an object
and a collaborator worked together as we expected. A great
example of an interaction is a "bean" style object, like perhaps
a "User", and the DAO for that object, like a UserDAO
• Assertion Patterns
•
"Delta" Assertion
•
Sometimes you can't assert an absolute equality (like "My list is now 5
elements long"). Sometimes, you have to assert equality relative to
some previous state. Imagine you're hooking into some scheduling
mechanism, for example. We don't know exactly what
getTotalScheduled() will return at any given test run. Maybe it's 1.
Maybe it's 30. Who knows. What we want to test is that when we
schedule one additional thing, our scheduler's "totalScheduled" count
increases by 1.
•
This type of assertion, where we compare the state right
before and right after performing some task, is
called "delta", or difference, assertion.
• Creating and Running a TestSuite
A TestSuite is a collection of tests that logically fit together.
•
•
MXUnit was built to make it as easy as possible to create tests
and test suites.
The steps for creating and running a TestSuite are:
1.
Create a ColdFusion page to run the suite
2.
Create a TestSuite object
3.
Tell the TestSuite what tests to add
4.
run() the TestSuite
5.
Print the output
6.
Run the suite in your web browser
• Additional Resources
•
http://www.mxunit.org
•
Test Driven Development: By Example
•
Test Driven: TDD and Acceptance TDD for Java Developers
•
ColdFusion 9 Developer Tutorial – Chapter 14
•
Marc Esher’s MXUnit Blog
Download