410 likes | 553 Views
Unit Testing with MxUnit. Jason Michl. Presentation Contents. Installation of MxUnit Introduction to Unit Testing Example Application Simple Unit Tests Test Suites Complex Unit Tests. Installation of MxUnit. http://www.mxunit.org/doc/index.cfm?doc=installframework Summary:
E N D
Unit Testing with MxUnit Jason Michl
Presentation Contents Installation of MxUnit Introduction to Unit Testing Example Application Simple Unit Tests Test Suites Complex Unit Tests
Installation of MxUnit http://www.mxunit.org/doc/index.cfm?doc=installframework Summary: Download. Unzip to root folder of website. Test: http://localhost/mxunit/index.cfm
Presentation Contents Installation of MxUnit Introduction to Unit Testing Example Application Simple Unit Tests Test Suites Complex Unit Tests
Introduction to Unit Testing Unit testing is a means to improve code quality and reliability. Individual units of code are tested according to specifications. Defects can be identified before integration with other code.
Test-Driven Development Test-Driven Development Write a test that fails Write code that causes the test to pass Repeat until code meets specifications
Asserts Asserts evaluate statements, and cause unit tests to fail when certain conditions are not met. Example: AssertTrue(x eq y); If x equals y, then continue with the test. If x doesnot equal y, then the test fails.
Asserts in MxUnit Built-in asserts (Reminder: Assert functions fail when their condition fails.) AssertTrue, AssertFalse, assertEquals, assertSame, assertNotSame Full list: http://wiki.mxunit.org/display/default/Built-In+Assertions
Asserts in MxUnit AssertTrue: Fails when condition is false. AssertTrue: Fails when condition is true. AssertEquals: Fails when arguments are not equal. AssertSame: Fails when arguments are not the same instance. (Arrays always fail.) AssertNotSame: Fails when arguments are the same instance. (Arrays always pass.)
Unit Testing in MxUnit MxUnit tests are written as components. Test components extend existing MxUnit components, and test functions through asserts.
Unit Testing in MxUnit Example syntax: <cfcomponent displayname="testProjects" extends="mxunit.framework.TestCase"> <cffunction name="A_getProjectList_Exists" access="remote" returntype="void"> <cfscript> ProjectGateway = createObject("component", "components.project.ProjectGateway"); assertTrue(isQuery(ProjectGateway.getProjectList())); </cfscript> </cffunction> </cfcomponent>
Presentation Contents Installation of MxUnit Introduction to Unit Testing Example Application Simple Unit Tests Test Suites Complex Unit Tests
BugTrack Example application Used to demonstrate test-driven development Practical framework to build unit tests around “Real-world” goal: organize software development projects
Specifications Tables Users User Types Projects Team Members Notes (Note: Tables already have test data in them.)
Test-Driven Development with BugTrack Requirements for “getProjectList” Return a list of valid project records Accept multiple optional arguments as filters
Test-Driven Development with BugTrack Basic tests for “getProjectList” Does function exist? Does function return a query? Does function return records from the database? Does function accept and use arguments?
Test-Driven Development with BugTrack Basic tests for “getProjectList” Does function return zero records, when no arguments have been passed? Does function return all records, when the “all” argument is passed?
Presentation Contents Installation of MxUnit Introduction to Unit Testing Example Application Simple Unit Tests Test Suites Complex Unit Tests
Test Code (Function has query?) Code that fails <cffunction name="A_getProjectList_Exists" access="remote" returntype="void"> <cfscript> projectGatewayDemo = createObject("component", "components.project.projectGatewayDemo"); assertTrue(isQuery(projectGatewayDemo.getProjectList())); </cfscript> </cffunction> <cffunction name="getProjectList" access="public" returntype="query" output="false"> </cffunction>
Test Code (Function has query?) Code that passes <cffunction name="A_getProjectList_Exists" access="remote" returntype="void"> <cfscript> projectGatewayDemo = createObject("component", "components.project.projectGatewayDemo"); assertTrue(isQuery(projectGatewayDemo.getProjectList())); </cfscript> </cffunction> <cffunction name="getProjectList" access="public" returntype="query" output="false"> <cfset var getProjectList = queryNew("empty") /> <cfreturn getProjectList /> </cffunction>
Test Code (Function returns records?) Code that fails <cffunction name="C_getProjectList_Returns_Records_Passes" access="remote" returntype="void"> <cfscript> projectGatewayDemo = createObject("component", "components.project.projectGatewayDemo"); assertTrue((projectGatewayDemo.getProjectList().recordCount gt 0), "No records were returned."); </cfscript> </cffunction> <cffunction name="getProjectList" access="public" returntype="query" output="false"> <cfset var getProjectList = queryNew("empty") /> <cfreturn getProjectList /> </cffunction>
Test Code (Function returns records?) Code that passes <cffunction name="C_getProjectList_Returns_Records_Passes" access="remote" returntype="void"> <cfscript> projectGatewayDemo = createObject("component", "components.project.projectGatewayDemo"); assertTrue((projectGatewayDemo.getProjectList().recordCount gt 0), "No records were returned."); </cfscript> </cffunction> • <cffunction name="getProjectList" access="public" returntype="query" output="false"> • <cfset var getProjectList = "" /> • <cfquery name="getProjectList" datasource="bugTrack"> • SELECT * • FROM prProjects • </cfquery> • <cfreturn getProjectList /> • </cffunction>
Test Code (Function takes arguments?) Code that fails <cffunction name="D_getProjectList_Passes_createdBy_Passes" access="remote" returntype="void"> <cfset projectGatewayDemo = createObject("component", "components.project.projectGatewayDemo") /> <cfset getProjectList = projectGatewayDemo.getProjectList(createdBy = 1) /> <cfloop query="getProjectList"> <cfset assertTrue(getProjectList.createdBy eq 1, "Projects were returned with the wrong creator.") /> </cfloop> </cffunction> • <cffunction name="getProjectList" access="public" returntype="query" output="false"> • <cfset var getProjectList = "" /> • <cfquery name="getProjectList" datasource="bugTrack"> • SELECT * • FROM prProjects • </cfquery> • <cfreturn getProjectList /> • </cffunction>
Test Code (Function takes arguments?) Code that passes <cffunction name="D_getProjectList_Passes_createdBy_Passes" access="remote" returntype="void"> <cfset projectGatewayDemo = createObject("component", "components.project.projectGatewayDemo") /> <cfset getProjectList = projectGatewayDemo.getProjectList(createdBy = 1) /> <cfloop query="getProjectList"> <cfset assertTrue(getProjectList.createdBy eq 1, "Projects were returned with the wrong creator.") /> </cfloop> </cffunction> • <cffunction name="getProjectList" access="public" returntype="query" output="false"> • <cfargument name="createdBy" default="" /> • <cfsetvargetProjectList = "" /> • <cfquery name="getProjectList" datasource="bugTrack"> • SELECT * • FROM prProjects • WHERE 0 = 0 • <cfiflen(arguments.createdBy)> • AND createdBy = <cfqueryparamcfsqltype="cf_sql_integer" value="#val(arguments.createdBy)#" /> • </cfif> • </cfquery> • <cfreturngetProjectList /> • </cffunction> (This test is repeated for approval status and projectID)
Test Code (Function requires arguments?) Code that fails <cffunction name="G_getProjectList_Returns_Zero_Records_Passes" access="remote" returntype="void"> <cfset projectGatewayDemo = createObject("component", "components.project.projectGatewayDemo") /> <cfset getProjectList = projectGatewayDemo.getProjectList() /> <cfset assertTrue(getProjectList.recordCount eq 0, "No arguments were sent. Query should have returned 0 results.") /> </cffunction> • <cffunction name="getProjectList" access="public" returntype="query" output="false"> • <cfargument name="createdBy" default="" /> • <cfsetvargetProjectList = "" /> • <cfquery name="getProjectList" datasource="bugTrack"> • SELECT * • FROM prProjects • WHERE 0 = 0 • <cfiflen(arguments.createdBy)> • AND createdBy = <cfqueryparamcfsqltype="cf_sql_integer" value="#val(arguments.createdBy)#" /> • </cfif> • </cfquery> • <cfreturngetProjectList /> • </cffunction>
Test Code (Function requires arguments?) Code that passes <cffunction name="G_getProjectList_Returns_Zero_Records_Passes" access="remote" returntype="void"> <cfset projectGatewayDemo = createObject("component", "components.project.projectGatewayDemo") /> <cfset getProjectList = projectGatewayDemo.getProjectList() /> <cfset assertTrue(getProjectList.recordCount eq 0, "No arguments were sent. Query should have returned 0 results.") /> </cffunction> • <cffunction name="getProjectList" access="public" returntype="query" output="false"> • <cfargument name="createdBy" default="" /> • <cfsetvargetProjectList = "" /> • <cfquery name="getProjectList" datasource="bugTrack"> • SELECT * • FROM prProjects • WHERE • <cfiflen(arguments.createdBy) OR len(arguments.approvalStatus) OR len(arguments.projectID)> • 0 = 0 • <cfelse> • 0 = 1 • </cfif> • <cfiflen(arguments.createdBy)> • AND createdBy = <cfqueryparamcfsqltype="cf_sql_integer" value="#val(arguments.createdBy)#" /> • </cfif> • </cfquery> • <cfreturngetProjectList /> • </cffunction>
Test Code (Function accepts “all” argument?) Code that fails <cffunction name="H_getProjectList_Returns_All_Records_Passes" access="remote" returntype="void"> <cfset projectGatewayDemo = createObject("component", "components.project.projectGatewayDemo") /> <cfset getProjectList = projectGatewayDemo.getProjectList(all = 1) /> <cfset assertTrue(getProjectList.recordCount gt 0, "The 'all' argument was sent. Query should have returned results.") /> </cffunction> • <cffunction name="getProjectList" access="public" returntype="query" output="false"> • <cfargument name="createdBy" default="" /> • <cfsetvargetProjectList = "" /> • <cfquery name="getProjectList" datasource="bugTrack"> • SELECT * • FROM prProjects • WHERE • <cfiflen(arguments.createdBy) OR len(arguments.approvalStatus) OR len(arguments.projectID)> • 0 = 0 • <cfelse> • 0 = 1 • </cfif> • <cfiflen(arguments.createdBy)> • AND createdBy = <cfqueryparamcfsqltype="cf_sql_integer" value="#val(arguments.createdBy)#" /> • </cfif> • </cfquery> • <cfreturngetProjectList /> • </cffunction>
Test Code (Function accepts “all” argument?) Code that passes <cffunction name="H_getProjectList_Returns_All_Records_Passes" access="remote" returntype="void"> <cfset projectGatewayDemo = createObject("component", "components.project.projectGatewayDemo") /> <cfset getProjectList = projectGatewayDemo.getProjectList(all = 1) /> <cfset assertTrue(getProjectList.recordCount gt 0, "The 'all' argument was sent. Query should have returned results.") /> </cffunction> • <cffunction name="getProjectList" access="public" returntype="query" output="false"> • <cfargument name="createdBy" default="" /> • <cfargument name="all" default="" /> • <cfsetvargetProjectList = "" /> • <cfquery name="getProjectList" datasource="bugTrack"> • SELECT * • FROM prProjects • WHERE • <cfiflen(arguments.createdBy) OR len(arguments.approvalStatus) OR len(arguments.projectID) OR len(arguments.all)> • 0 = 0 • <cfelse> • 0 = 1 • </cfif> • <cfiflen(arguments.createdBy)> • AND createdBy = <cfqueryparamcfsqltype="cf_sql_integer" value="#val(arguments.createdBy)#" /> • </cfif> • </cfquery> • <cfreturngetProjectList /> • </cffunction>
getProjectList Example: http://127.0.0.1/mxunit/testCollection/unitTests/projectGatewayTestDemo.cfc?flush=&method=runTestRemote&event=home&output=html (shows each iteration of the development cycle)
getProjectList Example: http://127.0.0.1/mxunit/testCollection/unitTests/projectGatewayTest.cfc?flush=&method=runTestRemote&event=home&output=html (shows final version of getProjectList)
Presentation Contents Installation of MxUnit Introduction to Unit Testing Example Application Simple Unit Tests Test Suites Complex Unit Tests
Test Suites Tests can be stored in multiple files and accessed as needed. The “getProjectList” test is one of many tests available for BugTrack.
Test Suites Other test files cover: Getting project member lists Getting project notes Getting user lists Getting user type lists
Test Suites Test Suites allow you to run multiple test files at the same time. Example: allGatewaySuite.cfm runs over 40 tests, across five files.
Test Suites Example syntax: <cfset testSuite = createObject("component","mxunit.framework.TestSuite").TestSuite()/> <cfset testSuite.addAll("mxunit.testcollection.unittests.projectGatewayTest")/> <cfset testSuite.addAll("mxunit.testcollection.unittests.projectMemberGatewayTest")/> <cfset testSuite.addAll("mxunit.testcollection.unittests.projectNotesGatewayTest")/> <cfset testSuite.addAll("mxunit.testcollection.unittests.userGatewayTest")/> <cfset testSuite.addAll("mxunit.testcollection.unittests.userTypeGatewayTest")/> <cfset results = testSuite.run()/> <cfset writeOutput(results.getResultsOutput('html'))/>
Test Suites (Show example suites in CFEclipse)
Test Suites Example: http://127.0.0.1/mxunit/testCollection/allGatewaySuite.cfm
Presentation Contents Installation of MxUnit Introduction to Unit Testing Example Application Simple Unit Tests Test Suites Complex Unit Tests
Complex Unit Tests Test-driven development can be used to successfully test complex applications. Example: BugTrack needs to pull a project, team members, and notes. We have unit tests for each of these, and now we need to test all of them together.
Complex Unit Tests (Show examples in CFEclipse)