A few year back, at the height of the iPod boom, I predicted its swift demise.
I might have titled this said blog post something like Why the iPod must die!
So back then I was predicting the death of single purpose devices such as MP3 players in general and the iPod in particular. Well... I was wrong, but not all the way. The iPod is still around, but sales are declining. As a matter of fact they have been declining every year since my prediction was made in 2008:
Why do I want to warm up old toast you ask? Good question. Another thing I mentioned towards the end of the aforementioned blog post was about, how I believed, Apple could make boatloads of money, not with the iPod itself, but with the control over the connection mechanism, the 30-pin dock.
Most "i" devices Apple introduced up to iPhone 4S have this connection mechanism. A whole supporting universe of accessory makers has emerged that use that 30-pin standard to connect anything from stereo systems to zebra pattern 3d printers (I made this one up, don't Google needlessly!).
However, Apple, like Sun, when it missed the JVM for the trees, missed to monetize the 30-pin connector handsomely. While normally very astute in locking in consumers and partners alike, this was a big miss, indeed, for Apple. Also contributing to this was the ease with which people could reverse engineer the connector.
But fret no more, under the guise of improving user experience, Apple introduced the new "lightning connector" and closed this loophole with the iPhone 5. Though I have not heard any of my friends or co-workers ever state that they had trouble or needed a new way to connect, it is now a fact of live that we will have to buy many adapters or replace existing gadgets.
Now, the life of the 3rd party accessory maker has changed drastically as well. No longer can they use simple analog techniques to reverse engineer this. There is an encryption chip specific to the connector that needs to be dealt with. Why would you need an encryption chip in the docking connector? To control its use of course. IMHO this thing is so complicated that it delayed the launch of the promised lightning to Apple dock adapter.
Thus, for manufacturers the only viable alternative is to check with Apple to see what the terms of licensing lightning technology would be. This, in turn, translates into revenue in the future for Apple, and my prediction made many years ago, finally comes true !
Cheers,
B.
Bilal's view on tech and code (Java, C#, vb, cfml, JavaScript, NodeJS, etc, etc.) and some random other musings.
Sunday, November 25, 2012
Tuesday, November 20, 2012
CF: ColdFusion with Amazon Load Balancer (ELB) and mysterious line breaks causing "unterminated string literal" exceptions
This seems like a complicated scenario at first until I started thinking about it again.
You use ColdFusion in the Cloud, specifically in the Amazon cloud, you then add a load balancer in front of your servers, then you notice your JavaScript starting to error out in some browsers. You cannot explain it.
Then, you spent countless hours going nuts.
Here is a sample JavaScript and ColdFusion block that would break. The simple block retrieved a name from a ColdFusion function and passed it to JavaScript:
<cfoutput>
<script type="text/javascript">
var myName = '#fCallForName()#';
</script>
</cfoutput>
The above returned this in browser lets say the name was "John" :
<script type="text/javascript">
var myName = '
John';
</script>
This, in turn, throws a "unterminated string literal" exception in JavaScript because of the line break right before the name. Of course, you say, silly you, you probably doing something in CF to return a Newline character combination. Good thought! So I changed the CF side to be as simple as possible. Here is the CF code that returns an empty string; guaranteed!
<cffunction name="fCallForName" returntype="string">
<cfreturn "">
</cffunction>
So nope, empty string still produced new line in the JavaScript output. So a few brain cycles go by and
I get to thinking to check to hit servers directly (bypassing amazon ELB altogether) and see what is returned. The return this time is enlightening, there is a blank string rather than the expected empty string, but no newline.
<script type="text/javascript">
var myName = ' ';
</script>
Yeah, progress, maybe? This is different, which again it should not be. Thus, the only thing this establishes is that the Amazon load balancer (ELB) is changing the output stream somehow. Then, I also remembered an earlier blog post of mine where I outlined that CF inserts an empty space character when the function output is used directly in place in HTML context: http://boncode.blogspot.com/2009/03/cf-coldfusion-functions-and-case-of.html
... and the fog began to lift.
I immediately reassigned the function output to a variable like so:
<cfset userName = fCallForName()>
Then, used the variable to output the content of the function:
<cfset userName = fCallForName()>
<cfoutput>
<script type="text/javascript">
var myName = '#userName#';
</script>
</cfoutput>
...and bingo, everything started to work again. No more JavaScript errors. However the conclusions here are more scary then the error:
a) ColdFusion does introduce more than just a space character when function output is used in place
b) Amazon elastic load balancer makes modifications (corrections?) to the network stream and changes the output. For each in place output of function call it adds a line break.
Both a) and b) should not happen, but they do ;o(
At least now my pain could be your gain. Cloud pittfals, I guess...simple assumptions like surely the load balancer will not modify my data could throw you off .
Cheers,
B.
You use ColdFusion in the Cloud, specifically in the Amazon cloud, you then add a load balancer in front of your servers, then you notice your JavaScript starting to error out in some browsers. You cannot explain it.
Then, you spent countless hours going nuts.
Here is a sample JavaScript and ColdFusion block that would break. The simple block retrieved a name from a ColdFusion function and passed it to JavaScript:
<cfoutput>
<script type="text/javascript">
var myName = '#fCallForName()#';
</script>
</cfoutput>
The above returned this in browser lets say the name was "John" :
<script type="text/javascript">
var myName = '
John';
</script>
This, in turn, throws a "unterminated string literal" exception in JavaScript because of the line break right before the name. Of course, you say, silly you, you probably doing something in CF to return a Newline character combination. Good thought! So I changed the CF side to be as simple as possible. Here is the CF code that returns an empty string; guaranteed!
<cffunction name="fCallForName" returntype="string">
<cfreturn "">
</cffunction>
So nope, empty string still produced new line in the JavaScript output. So a few brain cycles go by and
I get to thinking to check to hit servers directly (bypassing amazon ELB altogether) and see what is returned. The return this time is enlightening, there is a blank string rather than the expected empty string, but no newline.
<script type="text/javascript">
var myName = ' ';
</script>
Yeah, progress, maybe? This is different, which again it should not be. Thus, the only thing this establishes is that the Amazon load balancer (ELB) is changing the output stream somehow. Then, I also remembered an earlier blog post of mine where I outlined that CF inserts an empty space character when the function output is used directly in place in HTML context: http://boncode.blogspot.com/2009/03/cf-coldfusion-functions-and-case-of.html
... and the fog began to lift.
I immediately reassigned the function output to a variable like so:
<cfset userName = fCallForName()>
Then, used the variable to output the content of the function:
<cfset userName = fCallForName()>
<cfoutput>
<script type="text/javascript">
var myName = '#userName#';
</script>
</cfoutput>
...and bingo, everything started to work again. No more JavaScript errors. However the conclusions here are more scary then the error:
a) ColdFusion does introduce more than just a space character when function output is used in place
b) Amazon elastic load balancer makes modifications (corrections?) to the network stream and changes the output. For each in place output of function call it adds a line break.
Both a) and b) should not happen, but they do ;o(
At least now my pain could be your gain. Cloud pittfals, I guess...simple assumptions like surely the load balancer will not modify my data could throw you off .
Cheers,
B.
Thursday, October 18, 2012
CF: CFCamp 2012 more of everything
CFCamp 2012 is over. It was another whirlwind affair with more people, more speakers, more topics and more sponsors.
Overall a good gathering that had some nuggets to take away and think about.
Thanks everyone who stayed for my late presentation on practical application security.
You can download presentation slides if you want to review them at your leisure.
Cheers,
B.
Overall a good gathering that had some nuggets to take away and think about.
Thanks everyone who stayed for my late presentation on practical application security.
You can download presentation slides if you want to review them at your leisure.
Cheers,
B.
Friday, October 12, 2012
CF: CFCamp here we go
Another year, another CFCamp.
The ColdFusion faithful trek into the southerly realms of Munich, Germany to learn all about the intricacies of living the code - dream ;o) So I am my way to join the fine folks, raise a Stein, and catch up on all that is newsworthy.
There will be a selection of topics on HTML5 and mobile since this is a pattern that has been steadily gaining ground among CF'ers and is reflected in this conference.
In addition there is a smaller CFAcademy segment with expanded hands on training.
If you are participating in Security section of CFAcademy watch this blog as I will post some course materials shortly.
CFAcademy users please download your exercise materials.
You will need :
Cheers,
Bilal
The ColdFusion faithful trek into the southerly realms of Munich, Germany to learn all about the intricacies of living the code - dream ;o) So I am my way to join the fine folks, raise a Stein, and catch up on all that is newsworthy.
There will be a selection of topics on HTML5 and mobile since this is a pattern that has been steadily gaining ground among CF'ers and is reflected in this conference.
In addition there is a smaller CFAcademy segment with expanded hands on training.
If you are participating in Security section of CFAcademy watch this blog as I will post some course materials shortly.
CFAcademy users please download your exercise materials.
You will need :
- One of the CFML engines (Adobe, Railo, OpenBD) installed. I will use Railo to work through examples.
- A database: MySQL
- Create a data source "book1" using the sql dump.
Cheers,
Bilal
Saturday, September 29, 2012
Sencha: NCDevCon Presentation Materials
Thanks everyone for attending my presentation on Sencha Architect. I hope the Movie Finder project will provide some insight into how to use Architect to build / prototype your own apps.
I am taking a moment to post the materials I used; hope this makes things clear as mud :o)
Presentation Slides: MVC App in Hours (pdf)
Step-by-Step: Building the Movie Finder App in Sencha Architect (pdf)
Cheers,
B.
I am taking a moment to post the materials I used; hope this makes things clear as mud :o)
Presentation Slides: MVC App in Hours (pdf)
Step-by-Step: Building the Movie Finder App in Sencha Architect (pdf)
Cheers,
B.
Thursday, September 13, 2012
NCDevCon 2012: Regional Developer Conference with focus on Mobile, Web, and ColdFusion
Ok. Now it has been a cool four years that the Triangle Area
ColdFusion User’s Group (TACFUG) is putting on a conference NCDevCon 2012
(September 29./30.) for regional developers (everyone is welcome). The user group members are putting
in countless volunteer hours to create a conference for developers that
everyone can benefit from.
Though, as every year, there is some effort to focus the
presentations. This year the focus areas are Mobile, Web, and ColdFusion,
though there is a good selection of general topics as well.
Thus, the conference continues to manage to have broad coverage of
many relevant areas of ColdFusion, Web and Mobile development while also giving
beginners options for hands on sessions.
All this is available for a small fee ($200) compared to $800 to
$1000 of dollars we commonly pay. So this is definitely a deal in light of the
knowledge that is being shared.
I have been selected to do a
presentation on the Mobile side. This
year’s topic is “Design MVC Mobile App Visually In Hours”. I am doing a walk-through
of the Sencha architect tool as well as some other GUI development for mobile.
Lots of demoing so nothing will ever go wrong ;o)
There is still time to book and space available so I hope to connect
with everyone there.
Cheers,
-B
Wednesday, August 29, 2012
CF: MXunit Automatically Generating Tests and the Challenge of Test Scope
Let me start by saying that I like unit testing . It is very valuable tool in the arsenal of developers to fight the ever present monsters of recursion bugs and integration nightmares.
However, I will also freely admit that there is no agreement on how many unit tests are ever enough. Put a three developers in a room and you will get three different answers and maybe a headache to boot.
Also, in my case, the other challenge was to determine whether I had sufficient permutations of tests to verify that unit test goals could be met. Mind you that, test-coverage does not equal code coverage, but it probably is a good proxy. So, another goal of mine is to be able to think through all kinds of ways to call on code even the bad stuff and ensure that it behaves as expected. There is system in this, however, and this is what we use come up with most of the tests.
With MXUnit the world of ColdFusion has had a sturdy companion to write all these nice unit tests, however, we needed more. We wanted to have a good number of tests especially for cfc (ColdFusion components). Coming up with the variety and permutation of tests does require quite a bit of hand coding, so we were looking for an easier way.
The solution we came up with was to generate test stubs. Many, many, many test stubs. We then review the cases and expand the ones we think cover the objective sufficiently. We end up deleting quite a few generated test, but this gives us a good baseline, especially for things we don't commonly unit test but we should, e.g. sending in a number when a string is expected, sending complex values, when simple ones will do and vice versa.
This may not be the way you want to work all test cases but helps to take care of many. We look at a functions parameters and generate all combinations of parameters with standard and break values. This can amount to be many thousandths so use with care.
The generator sample code I am attaching has been squeezed into one code file (cfm template) so it is easier to post. Place the code content into a file named "GenerateTestsPublic.cfm" under your mxunit path. Not optimal but the concepts should be visible. Take it for a spin and make adjustments. Feel free to add comments to the blog post.
Happy Experimenting:
GenerateTestsPublic.cfm:
Cheers,
B.
However, I will also freely admit that there is no agreement on how many unit tests are ever enough. Put a three developers in a room and you will get three different answers and maybe a headache to boot.
Also, in my case, the other challenge was to determine whether I had sufficient permutations of tests to verify that unit test goals could be met. Mind you that, test-coverage does not equal code coverage, but it probably is a good proxy. So, another goal of mine is to be able to think through all kinds of ways to call on code even the bad stuff and ensure that it behaves as expected. There is system in this, however, and this is what we use come up with most of the tests.
With MXUnit the world of ColdFusion has had a sturdy companion to write all these nice unit tests, however, we needed more. We wanted to have a good number of tests especially for cfc (ColdFusion components). Coming up with the variety and permutation of tests does require quite a bit of hand coding, so we were looking for an easier way.
The solution we came up with was to generate test stubs. Many, many, many test stubs. We then review the cases and expand the ones we think cover the objective sufficiently. We end up deleting quite a few generated test, but this gives us a good baseline, especially for things we don't commonly unit test but we should, e.g. sending in a number when a string is expected, sending complex values, when simple ones will do and vice versa.
This may not be the way you want to work all test cases but helps to take care of many. We look at a functions parameters and generate all combinations of parameters with standard and break values. This can amount to be many thousandths so use with care.
The generator sample code I am attaching has been squeezed into one code file (cfm template) so it is easier to post. Place the code content into a file named "GenerateTestsPublic.cfm" under your mxunit path. Not optimal but the concepts should be visible. Take it for a spin and make adjustments. Feel free to add comments to the blog post.
Happy Experimenting:
GenerateTestsPublic.cfm:
<!---
Generate mxUnit tests given a component name.
One File per component function will be generated in this directory.
Will attempt to generate all permutations possible.
Random values
Break Values
Prefix:
Test_[componentName]_[functionName].cfc
An overall TestSuite will be generated
TestSuite_[componentName].cfm
Distributed under Apache 2 Lincese
(c) 2012 Bilal Soylu
--->
<!DOCTYPE HTML>
<html>
<head>
<title>Generate MXUnit Test for Components</title>
</head>
<body>
<CFIF IsDefined("Form.objectName") AND Trim(Form.objectName) NEQ "">
<!--- get object info --->
<cftry>
<cfset objReg = CreateObject("COMPONENT","#Trim(Form.objectName)#")>
<cfset stcMeta = getMetaData(objReg)>
<!--- generate directory --->
<!--- generate test files --->
<!--- save object name --->
<cfscript>
//save main object
strName = UCase(Trim(Form.objectName));
strUseObjectNameInDir = ReplaceNoCase(strName,".","_","ALL");
strDirPrefix = "unitTests";
strHint="";
arrTestCaseNames = [];
//max test cases (high number of combinations can exist
intMaxCases = -1;
intMaxCasesPerFile = 1000;
if (IsDefined("Form.maxTests") and Val(Form.maxTests) GT 0) intMaxCases = Val(Form.maxTests);
if (IsDefined("Form.maxTestsPerFile") and Val(Form.maxTestsPerFile) LT intMaxCases) intMaxCasesPerFile = Val(Form.maxTestsPerFile);
//names & paths
strBasePath = GetDirectoryFromPath(GetCurrentTemplatePath()) & strDirPrefix & "\";
strFilePrefix = "Test_#strUseObjectNameInDir#_";
strTestSuiteName = "TestSuite_#strUseObjectNameInDir#.cfm";
strTestSuitePath = strBasePath & strTestSuiteName;
strTestCaseDirName = "TestCases_#strUseObjectNameInDir#";
strTestCaseDirPath = strBasePath & "" & strTestCaseDirName;
blnComplete=false;
crlf = chr(13) & chr(10);
tab = chr(9);
//create directory
if (NOT DirectoryExists(strTestCaseDirPath)) DirectoryCreate(strTestCaseDirPath);
//sample data
sampleStructure= ":" & SerializeJSON({"number"=9999,'text'='my text value','dte'=Now()});
sampleArray= ":" & SerializeJSON(["a","b",33,{"number"=9999,'text'='my text value','dte'=Now()}]);
sampleQuery = QueryNew("");
FastFoodArray = ["French Fries","Hot Dogs","Fried Clams","Thick Shakes"];
nColumnNumber = QueryAddColumn(sampleQuery, "FastFood", "VarChar", FastFoodArray);
</cfscript>
<!--- assemble files --->
<cfif IsDefined("stcMeta.Functions") AND ArrayLen(stcMeta.Functions) GT 0>
<cfloop index="i" from="1" to="#ArrayLen(stcMeta.Functions)#" step="1">
<!--- each functions has its on test case. init var containers --->
<cfset stcFunc = stcMeta.Functions[i]>
<cfset selParaCombinations = QueryNew("")>
<cfset stcQueries = {}>
<cfparam name="stcFunc.Access" default="public">
<cfif stcFunc.Access IS "public">
<cfset strMethod = UCase(stcFunc.name)>
<cfset strMethodHint = "">
<cfset strTestCaseName = strFilePrefix & strMethod>
<cfset strTestCaseFileName= strTestCaseDirPath & "\#strTestCaseName#.cfc">
<!--- output (bsoylu 03-29-2012) --->
<cfoutput>
<br>processing: #strMethod#<br>
</cfoutput>
<!--- start testCase tC variable --->
<cfset genStartTc()>
<!--- init --->
<cfset arrParams = stcFunc.Parameters>
<!--- iterate through each paramter and set base test values --->
<cfif (ArrayLen(arrParams) GT 0)>
<cfset strQList = "">
<cfloop from="1" to="#ArrayLen(arrParams)#" index="y">
<cfset stcPara = arrParams[y]>
<cfset strParaName= UCase(stcPara.Name)>
<!--- create query and query handle --->
<cfset stcQueries["q_#strParaName#"] = QueryNew(strParaName,"CF_SQL_VARCHAR")>
<cfset selQ = stcQueries["q_#strParaName#"]>
<cfset strQList = ListAppend(strQList,"q_#strParaName#")>
<!--- if parameter is not required it can be null as a valid state --->
<cfif NOT (IsDefined("stcPara.Required") AND stcPara.Required)>
<cfset QueryAddRow(selQ)>
<cfset QuerySetCell(selQ,strParaName,"NULL")>
</cfif>
<!--- by type --->
<!--- we prefix with equal sign if we need to eval the data later in processing --->
<!--- we prefix with colon (:) when we need to deserialize JSON --->
<cfif IsDefined("stcPara.Type")>
<cfif stcPara.Type IS "numeric">
<!--- for each numeric: use max, use min, use zero, use random --->
<!--- Java Long: 9223372036854775807 and -9223372036854775808 --->
<cfset QueryAddRow(selQ)>
<cfset QuerySetCell(selQ,strParaName,"999999999")>
<cfset QueryAddRow(selQ)>
<cfset QuerySetCell(selQ,strParaName,"-999999999")>
<cfset QueryAddRow(selQ)>
<cfset QuerySetCell(selQ,strParaName,"0")>
<cfset QueryAddRow(selQ)>
<cfset QuerySetCell(selQ,strParaName,"#RandRange(0,999999999)#")>
</cfif>
<cfif stcPara.Type IS "string">
<!--- for each string: use max, use empty, use "coldFusion" --->
<cfset strLongString = RepeatString("a",4000)>
<cfset QueryAddRow(selQ)>
<cfset QuerySetCell(selQ,strParaName,"#strLongString#")>
<cfset QueryAddRow(selQ)>
<cfset QuerySetCell(selQ,strParaName,"")>
<cfset QueryAddRow(selQ)>
<cfset QuerySetCell(selQ,strParaName,"coldFusion")>
</cfif>
<cfif stcPara.Type IS "struct" OR stcPara.Type IS "any" >
<!--- for each struct: use empty, use test struct --->
<cfset QueryAddRow(selQ)>
<cfset QuerySetCell(selQ,strParaName,sampleStructure)>
<cfset QueryAddRow(selQ)>
<cfset QuerySetCell(selQ,strParaName,"=StructNew()")>
</cfif>
<cfif stcPara.Type IS "date" >
<!--- for each date: use "1/1/1980" use today use tomorrow, use 12/31/2200 --->
<cfset QueryAddRow(selQ)>
<cfset QuerySetCell(selQ,strParaName,"=CreateDate(1980,1,1)")>
<cfset QueryAddRow(selQ)>
<cfset QuerySetCell(selQ,strParaName,"=Now()")>
<cfset tomorrow = DateAdd("d",1,Now())>
<cfset QueryAddRow(selQ)>
<cfset QuerySetCell(selQ,strParaName,"=CreateDate(#Year(tomorrow)#,#Month(tomorrow)#,#Day(tomorrow)#)")>
<cfset QueryAddRow(selQ)>
<cfset QuerySetCell(selQ,strParaName,"=CreateDate(2200,12,31)")>
</cfif>
<cfif stcPara.Type IS "array" >
<!--- for each array: add sample array and empty --->
<cfset QueryAddRow(selQ)>
<cfset QuerySetCell(selQ,strParaName,"=ArrayNew(1)")>
<cfset QueryAddRow(selQ)>
<cfset QuerySetCell(selQ,strParaName,sampleArray)>
</cfif>
<cfif stcPara.Type IS "boolean" >
<!--- for each bool: true/false --->
<cfset QueryAddRow(selQ)>
<cfset QuerySetCell(selQ,strParaName,"Yes")>
<cfset QueryAddRow(selQ)>
<cfset QuerySetCell(selQ,strParaName,"No")>
</cfif>
<cfif stcPara.Type IS "query" >
<!--- for each query: sample and empty --->
<cfset QueryAddRow(selQ)>
<cfset QuerySetCell(selQ,strParaName,":#SerializeJSON(sampleQuery)#")>
<cfset QueryAddRow(selQ)>
<cfset QuerySetCell(selQ,strParaName,":#SerializeJSON(QueryNew(""))#")>
</cfif>
<cfelse>
<!--- valid state for non-typed or unknown paramters --->
<!--- for each undefined type (any): use structure --->
<cfset QueryAddRow(selQ)>
<cfset QuerySetCell(selQ,strParaName,sampleStructure)>
</cfif>
<!--- for the first parameter query we also set the query result. If there is only one parameter this is what will be returned --->
<cfif y IS 1>
<cfset selParaCombinations = selQ>
</cfif>
</cfloop><!--- loop through paramters --->
<!--- build cartesian product of queries (this will show us all the needed combinations --->
<cfset qCounter = 0>
<cfloop list="#strQList#" index="qName">
<cfset qCounter ++>
<cfif qCounter GT 1>
<!--- combine into cartesian set. We can only do two queries at a time --->
<cfset qToAdd = stcQueries[qName]>
<cfquery name="selParaCombinations" dbtype="query">
SELECT DISTINCT *
FROM selParaCombinations,qToAdd
</cfquery>
</cfif>
</cfloop>
<!--- determine max loop (bsoylu 03-30-2012) --->
<cfset intMaxLoop = selParaCombinations.RecordCount>
<cfif intMaxCases GT 0>
<cfset intMaxLoop = intMaxCases>
</cfif>
<cfoutput>
found #selParaCombinations.RecordCount# test combinations
<cfif intMaxCases GT 0 AND intMaxCases LT selParaCombinations.RecordCount>
<b>only #intMaxCases#</b> will be generated
</cfif>
<br>
</cfoutput>
<cfflush>
<!--- loop and generate test for file (bsoylu 03-29-2012) --->
<cfset intTestCount = 0>
<cfset intFileCount = 0>
<cfloop query="selParaCombinations" endrow="#intMaxLoop#">
<cfset tf="">
<cfset intTestCount ++ >
<!--- create para structure (bsoylu 03-29-2012) --->
<cfset stcPara = {}>
<cfloop list="#selParaCombinations.ColumnList#" index="idxCol">
<cfset strVal=Evaluate("selParaCombinations.#idxCol#")>
<!--- check whether we need to further process the content (bsoylu 03-29-2012) --->
<cfif strVal neq "NULL">
<cftry>
<cfif strVal is "">
<cfset stcPara[idxCol] = strVal>
<cfelseif Left(strVal,1) IS ":">
<cfset stcPara[idxCol] = DeserializeJSON( Right(strVal,Len(strVal)-1))>
<cfelseif Left(strVal,1) IS "=">
<cfset stcPara[idxCol] = Evaluate( Right(strVal,Len(strVal)-1))>
<cfelse>
<cfset stcPara[idxCol] = strVal>
</cfif>
<cfcatch>
<hr>
<cfoutput>could not interpret #Right(strVal,Len(strVal)-1)#</cfoutput>
</hr>
<cfabort>
</cfcatch>
</cftry>
</cfif>
</cfloop>
<!--- create function for this para combination (bsoylu 03-29-2012) --->
<cfset strJSON = SerializeJSON(stcPara)>
<cfset tf=tf & crlf>
<cfset tf=tf & crlf & tab & '<cffunction name="test_#strMethod#_#NumberFormat(selParaCombinations.CurrentRow,"00000")#" >''>
<cfset tf=tf & crlf & tab & tab & '<cfset var strJSONPara =''' & strJSON &'''>''>
<cfset tf=tf & crlf & tab & tab & '<cfset var response ="">''>
<cfset tf=tf & crlf & tab & tab & '<cfset var argCol = DeserializeJSON(strJSONPara)>''>
<cfset tf=tf & crlf & tab & tab & '<cfinvoke argumentcollection="##argCol##" component="#strName#" method="#strMethod#" returnvariable="response"/>''>
<cfset tf=tf & crlf & tab & tab & '<cfset assertNotEquals( "",response,SerializeJSON(response) & " - failed call with' & Replace(strJSON,'"','""','ALL') & '")>''>
<cfset tf=tf & crlf & tab & '</cffunction>''>
<cfset tf=tf & crlf>
<!--- add to test file content (bsoylu 03-29-2012) --->
<cfset tc=tc & crlf & tf>
<cfif intTestCount mod intMaxCasesPerFile IS 0>
<!--- write what we have so far (bsoylu 04-03-2012) --->
<cfset intFileCount++>
<cfset strModTestCaseName = strTestCaseName & "_File" & intFileCount>
<cfset tC = tC & crlf & '</cfcomponent>''>
<!--- set numbered File names (bsoylu 04-03-2012) --->
<cfset strModTestCaseFileName= strTestCaseDirPath & "\#strModTestCaseName#.cfc">
<cfset ArrayAppend(arrTestCaseNames,strModTestCaseName)>
<cffile action="WRITE" file="#strModTestCaseFileName#" output="#tc#" addnewline="No">
<cfoutput>generated sub case file: #strModTestCaseName#<br></cfoutput>
<!--- reset tc for next file (bsoylu 04-03-2012) --->
<cfset genStartTc("_File" & intFileCount + 1)>
</cfif>
</cfloop> <!--- set parameters (bsoylu 03-29-2012) --->
</cfif> <!--- we have parameters --->
<!--- complete the last file (bsoylu 04-03-2012) --->
<cfset tC = tC & crlf & '</cfcomponent>''>
<cfset ArrayAppend(arrTestCaseNames,strTestCaseName)>
<!--- write test case --->
<cffile action="WRITE" file="#strTestCaseFileName#" output="#tc#" addnewline="No">
<cfoutput>generated last case file: #strTestCaseName#<br></cfoutput>
</cfif> <!--- public function --->
</cfloop> <!--- loop through functions --->
<!--- generate test suite file --->
<cfset genTestSuite()>
<cfset blnComplete = true>
</cfif>
<hr>
<cfcatch type="Any">
<font color="red" size="+2">
There was an error. Please ensure that your component is located in the correct directory: <BR>
</font>
<table cellspacing="2" cellpadding="2" border="1">
<tr>
<td><cfdump var="#cfcatch#">
</td>
</tr>
</table>
<HR color="#ff0000" noshade>
</cfcatch>
</cftry>
</CFIF>
<cffunction name="genTestSuite">
<cfset var tcName ="">
<cfset tf = crlf>
<cfset tf=tf & crlf & '<cfparam name="URL.output" default="extjs">''>
<cfset tf=tf & crlf & '<cfset testSuitePath = "mxunit.framework.TestSuite" >''>
<cfset tf=tf & crlf & '<cfset testSuite = createObject("component", testSuitePath).TestSuite() >''>
<cfloop from="1" to="#ArrayLen(arrTestCaseNames)#" index="idxArr">
<cfset tcName = arrTestCaseNames[idxArr]>
<cfset tf=tf & crlf & '<cfset uTest = createObject("component", "#strTestCaseDirName#.#tcName#")>''>
<cfset tf=tf & crlf & '<cfset testSuite.addAll("#tcName#", uTest) >''>
<cfset tf=tf & crlf>
</cfloop>
<cfset tf=tf & crlf>
<cfset tf=tf & crlf & '<cfset results = testSuite.run() >''>
<cfset tf=tf & crlf & '<cfset out = results.getResultsOutput(URL.output)>''>
<cfset tf=tf & crlf & '<cfif NOT IsSimpleValue(out)>''>
<cfset tf=tf & crlf & tab & '<cfdump var="##out##">''>
<cfset tf=tf & crlf & '<cfelse>''>
<cfset tf=tf & crlf & tab & '<cfoutput>'##out##</cfoutput>'>
<cfset tf=tf & crlf & '</cfif>''>
<!--- loop through and add tests (bsoylu 03-29-2012) --->
<cffile action="WRITE" output="#tf#" file="#strTestSuitePath#" addnewline="No">
</cffunction>
<cffunction name="genStartTc">
<cfargument name="strModifier" type="string" default="" hint="modifier for component name">
<!--- start testCase tC --->
<cfset tC = '<cfcomponent displayname="MxunitTestCase_#strTestCaseName##Arguments.strModifier#" extends="mxunit.framework.TestCase">''>
<!--- empty call no parameters --->
<cfif Arguments.strModifier IS "">
<cfset tc=tc & crlf & tab & '<cffunction name="testNoParams_#strMethod#" >''>
<cfset tc=tc & crlf & tab & tab & '<cfset var response ="">''>
<cfset tc=tc & crlf & tab & tab & '<cfinvoke component="#strName#" method="#strMethod#" returnvariable="response"/>''>
<cfset tc=tc & crlf & tab & tab & '<cfset assertNotEquals( "",response,"No parameter call failed")>''>
<cfset tc=tc & crlf & tab & '</cffunction>''>
</cfif>
</cffunction>
<cfif IsDefined("blnComplete") and blnComplete>
<font size="+2" color="#008000">
<cfoutput>
Successfully generated Test Suite for Component [#UCase(Form.objectName)#]. <BR>
Test suite file : <a href="#strDirPrefix#/#strTestSuiteName#" target="_blank" title="run tests">[#strTestSuitePath#]</a> <BR>
Please review and adjust specific test cases.
</cfoutput>
</font>
<cfabort>
</cfif>
<h2>Welcome</h2>
<BR>
This program will help you generate mxUnit test cases.
It requires access to components to be analyzed.
<BR>
After initial generation you should make changes as the assertion are generic.
The objective is provide broad test coverage.
<HR>
Behavior Notes <BR>
<PRE>
All files will be generated in the "unitTests" subdirectory based on the initial directory this template is run from
Will attempt to generate all permutations possible.
Random values
Break Values
Test File Name:
TestCases_[componentName]\Test_[componentName]_[functionName].cfc
If multiple files are generated per function, a File[n] postfix wil be added except the last one:
TestCases_[componentName]\Test_[componentName]_[functionName]_File[n].cfc
An overall TestSuite will be generated
Test Suite name:
TestSuite_[componentName].cfm
</PRE>
<form method="post" action="GenerateTestsPublic.cfm">
<table cellspacing="2" cellpadding="2" border="0">
<tr>
<td>Component Path from the webroot (e.g. component located [root]/myweb/cfcs/foo.cfc would be myweb.cfcs.foo)</td>
<td><input type="Text" name="objectName" value=""></td>
</tr>
<tr>
<td>Max Number of Test Cases per File (same function)</td>
<td><input type="Text" name="maxTestsPerFile" value="1000"> (multiple files will be generated if more than this number)</td>
</tr>
<tr>
<td>Max Number of Test Cases for a Function</td>
<td><input type="Text" name="maxTests" value="5000"></td>
</tr>
<tr>
<td></td>
<td><input type="submit" name="submit" value="submit"></td>
</tr>
</table>
</form>
</body>
</html>
Cheers,
B.
Subscribe to:
Posts (Atom)
