In this, the sixth part of a series on unit testing classic ASP, we will continue with our test-first development of the class we created in part five. All the previous parts are available at:
Part one: how to unit test classic ASP
Part two: user stories for classic ASP unit testing example
Part three: beginnings of a classic ASP unit test framework
Part four: automating classic ASP unit tests with NUnit
Part five: using the classic ASP unit test framework
We last left our OrderSettings class and its unit tests in a green pass state, but the CanOrderAt function was still hardcoded. We therefore need to include a new assertion in our unit test ASP file "OrderSettingsTest.asp" to test for a different time:
Dim onePm, twoPm, tenAM onePm = "10/10/2008 13:00:00" twoPm = "10/10/2008 14:00:00" tenAM = "10/10/2008 10:00:00" tester.AssertIsFalse currentOrderSettings.CanOrderAt(onePm), "Can Not Order At 13:00" tester.AssertIsTrue currentOrderSettings.CanOrderAt(twoPm), "Can Order At 14:00" tester.AssertIsTrue currentOrderSettings.CanOrderAt(tenAm), "Can Order At 10:00"
The unit tests now fail on the last assertion so it is time for us to introduce some real code in the CanOrderAt function:
Public Function CanOrderAt(dateTime) Dim checkDate, cutOffDate, nextDate dateTime = CDate(dateTime) ' first strip the time off the date sent into this function checkDate = DateAdd("h",0-DatePart("h",dateTime),dateTime) checkDate = DateAdd("n",0-DatePart("n",dateTime),checkDate) checkDate = DateAdd("s",0-DatePart("s",dateTime),checkDate) ' now use this and add the time from the cutOffTime property cutOffDate = DateAdd("h",DatePart("h",Me.CutOffTime),checkDate) cutOffDate = DateAdd("n",DatePart("n",Me.CutOffTime),cutOffDate) cutOffDate = DateAdd("s",DatePart("s",Me.CutOffTime),cutOffDate) ' add the time from the nextOrderTime property nextDate = DateAdd("h",DatePart("h",Me.NextOrderTime),checkDate) nextDate = DateAdd("n",DatePart("n",Me.NextOrderTime),nextDate) nextDate = DateAdd("s",DatePart("s",Me.NextOrderTime),nextDate) CanOrderAt = (dateTime < cutOffDate) Or (dateTime >= nextDate) End Function
This is our first attempt at the code for this method - it strips the time off the original date and stores this in a variable called "checkDate"; we then derive two new variables by adding the time from our properties to this date so that the day remains the same as the one passed in. We can now compare the times and return whether ordering is possible.
When we run our unit tests, they pass.
There is obviously room for refactoring here, so we will now test-first a new class that will handle common date operations for us and enable us to change the code we have just written. We start by creating a new folder in our Unit test area called "Utilitites" and creating a new "DateUtilityFixture.cs" file:
namespace ASPTesting.Tests.UnitTests.Utilities { [TestFixture] public class DateUtilityFixture : AspFixture { [Test] public void DateUtilityTest() { RunAspTest("UnitTests/Utilities/DateUtilityTest.asp"); } } }
This code is expecting a new DateUtilityTest.asp file - if we run the unit tests now, they fail due to a server error, so let's create the new test file:
<!--#include virtual="Tests/UnitTester.asp"--> <!--#include virtual="Utilities/DateUtility.asp"--> <% dim tester Set tester = new UnitTester tester.Init("Testing Date Utility") Dim dateUtil Set dateUtil = new DateUtility %>
Here we have an include for a file called "DateUtility" in our main project that does not yet exist and we set up the unit tester and create an instance of the DateUtility class. If we run the unit test for this it fails because of the missing DateUtility class, but before we write it, we should introduce some tests:
Dim dateUtil Set dateUtil = new DateUtility dim aDate, expectedDate, baseDate aDate = CDate("10/10/2008 21:53:46") expectedDate = CDate("10/10/2008 00:00:00") tester.Given("a date of " + CStr(aDate) + " and we are testing StripTime") tester.AssertAreEqual dateUtil.StripTime(aDate), expectedDate aDate = CDate("10/10/2008 00:00:00") tester.Given("a date of " + CStr(aDte) + " and we are testing StripTime") tester.AssertAreEqual dateUtil.StripTime(aDate), expectedDate
We now have two tests for a StripTime function that we can now write in a new file in our main project - DateUtility. We have the stripping code already inside our OrderSettings class at the moment so we copy that code into our new class and refine it:
<% Class DateUtility Public Function StripTime(inputDate) dim newDate inputDate = CDate(inputDate) newDate = DateAdd("h",0-DatePart("h",inputDate),inputDate) newDate = DateAdd("n",0-DatePart("n",inputDate),newDate) newDate = DateAdd("s",0-DatePart("s",inputDate),newDate) StripTime = newDate End Function End Class %>
We now run our unit tests and they all pass. We could now add some more edge cases in for this function, but for this exercise we will continue onto refactoring our main OrderSettings class.
Because we have unit tests surrounding both the date utility function and the OrderSettings CanOrderAt function we can now refactor with some level of confidence and we see the true value of unit tests with classic ASP - we can confidently change the code! Let's have a look at the new OrderSettings code:
<!--#include virtual="Utilities/DateUtility.asp"--> <% Class OrderSettings Private m_cutOffTime Private m_nextOrderTime Public Property Get CutOffTime() CutOffTime = m_cutOffTime End Property Public Property Let CutOffTime(value) m_cutOffTime = CDate(value) End Property Public Property Get NextOrderTime() NextOrderTime = m_nextOrderTime End Property Public Property Let NextOrderTime(value) m_nextOrderTime = CDate(value) End Property Public Function CanOrderAt(dateTime) Dim checkDate, cutOffDate, nextDate, dateUtil Set dateUtil = new DateUtility dateTime = CDate(dateTime) checkDate = dateUtil.StripTime(dateTime) ' now use this and add the time from the cutOffTime property cutOffDate = DateAdd("h",DatePart("h",Me.CutOffTime),checkDate) cutOffDate = DateAdd("n",DatePart("n",Me.CutOffTime),cutOffDate) cutOffDate = DateAdd("s",DatePart("s",Me.CutOffTime),cutOffDate) ' add the time from the nextOrderTime property nextDate = DateAdd("h",DatePart("h",Me.NextOrderTime),checkDate) nextDate = DateAdd("n",DatePart("n",Me.NextOrderTime),nextDate) nextDate = DateAdd("s",DatePart("s",Me.NextOrderTime),nextDate) CanOrderAt = (dateTime < cutOffDate) Or (dateTime >= nextDate) End Function End Class %>
We have a new include declaration at the top of the file for the new DateUtility class and now our strip time functionality is one line (and we no longer need the comment as it is self explanatory).
We can now clean the rest of the code by introducing a new AddTime method to the DateUtility class. As always, we start with some new tests in the DateUtilityTest.asp file:
aDate = CDate("10/10/2008 00:00:00") baseDate = CDate("01/01/2007 23:59:01") expectedDate = CDate("10/10/2008 23:59:01") tester.Given("a date of " + CStr(aDate) + " and a baseDate of " + CStr(baseDate) + " and we are testing AddTime") tester.AssertAreEqual dateUtil.AddTimeFrom(aDate, baseDate), expectedDate
These two new assertions cause our unit tests to fail because the function is not available. We therefore create it and add the code in from our OrderSettings class.
(Note that you could, in true red/green/refactor style, create the function and return a nominal date value first, thus causing the test to fail from an assertion as opposed to a runtime error. This would ensure your test fail for the correct reason - we are not doing so throughout here for brevity).
<% Class DateUtility Public Function StripTime(inputDate) dim newDate inputDate = CDate(inputDate) newDate = DateAdd("h",0-DatePart("h",inputDate),inputDate) newDate = DateAdd("n",0-DatePart("n",inputDate),newDate) newDate = DateAdd("s",0-DatePart("s",inputDate),newDate) StripTime = newDate End Function Public Function AddTimeFrom(inputDate, fromDate) dim newDate inputDate = CDate(inputDate) fromDate = CDate(fromDate) newDate = DateAdd("h",DatePart("h", fromDate),inputDate) newDate = DateAdd("n",DatePart("n", fromDate),newDate) newDate = DateAdd("s",DatePart("s", fromDate),newDate) AddTimeFrom = newDate End Function End Class %>
We now have our function coded and when we run our unit tests, they now pass. Again, we could add edge cases here and better describe our desired behaviour for this class, but we will continue on in this example.
We now refactor the CanOrderAt function of the OrderSettings class:
Public Function CanOrderAt(dateTime) Dim checkDate, cutOffDate, nextDate, dateUtil Set dateUtil = new DateUtility dateTime = CDate(dateTime) checkDate = dateUtil.StripTime(dateTime) cutOffDate = dateUtil.AddTimeFrom(checkDate,Me.CutOffTime) nextDate = dateUtil.AddTimeFrom(checkDate,Me.NextOrderTime) CanOrderAt = (dateTime < cutOffDate) Or (dateTime >= nextDate) End Function
When we run our unit tests, they all pass!
This part has illustrated how we can do some true test-first development with classic ASP and bring some good practices such as encapsulation and single responsibility principle to our code. We refactored our code with confidence because we had tests surrounding the code that we were adding and modifying.
We have now provided the functionality we need for our first user story that we were exploring:
As a user if I access the system between 13:00 and 14:00, I cannot place an order and the system will provide an appropriate message.
Our order page would use this CanOrderAt function of our new API to evaluate whether ordering was possible for the current time and would display a message accordingly (we are not going to code that page or any other main ASP page in our unit test examples and simply explore our unit testing examples).
In the next part of this series we will explore some more classic ASP unit testing and attempt to deliver more functionality for our user story backlog.
3 comments: