Yesterday’s message:
Writing tests for legacy code – Part 4 | Access JumpStart
Here is the full code I am discussing:
'@TestMethod("DeleteAction")
Private Sub GivenConcreteGroupDeleteTriggered_WhenReadOnlyStatusType_RequirePassword()
On Error GoTo TestFail
'Arrange:
Dim LineController As ECI_POLineController
Set LineController = New ECI_POLineController
Dim testDictionary As New Scripting.Dictionary
testDictionary.Add "Cost_Type_ID", "2"
testDictionary.Add "Product_Type_ID", "1"
testDictionary.Add "Qty_Ordered", "10"
testDictionary.Add "Phase_Number", "10"
testDictionary.Add "Item_Description", "Test Description"
testDictionary.Add "Qty_Used", "10"
testDictionary.Add "Unit_of_Measure", "EA"
testDictionary.Add "Taxable", "True"
testDictionary.Add "Concrete_Minutes_Apart", "15"
testDictionary.Add "Line_Status", STATUS_OPEN
'Act:
Dim frmGet As New FormValueGetter_Test
Dim PwCount As New T_PasswordsAlwaysTrueCounter
Set frmGet.FieldValues = testDictionary
Set LineController.frmGet = frmGet
Set LineController.Controller = New T_PoScreenControllerAddModeWritable
LineController.ExecFormDelete New T_StatusConcreteGroupLinesReadOnly, PwCount
'Assert:
Assert.AreEqual CLng(1), PwCount.Counter
TestExit:
'@Ignore UnhandledOnErrorResumeNext
On Error Resume Next
Exit Sub
TestFail:
Assert.Fail "Test raised an error: #" & Err.Number & " - " & Err.Description
Resume TestExit
End Sub
The central piece of this test is the following two lines of code:
LineController.ExecFormDelete New T_StatusConcreteGroupLinesReadOnly, PwCount
'Assert:
Assert.AreEqual CLng(1), PwCount.Counter
The first line is the last line of the Act phase. I am wanting to run the ExecFormDelete Method from the LineController. This is what happens when a line is deleted on the line items form. The other code before this was to configure the LineController for the test environment and to set up the objects needed to define the form state and to simulate asking for a password.
In the live environment, the LineController.ExecFormDelete is called from the Form_OnDelete event and handles whether to cancel the event or not. If the event is not cancelled, the line will be deleted. The T_StatusConcreteGroupLinesReadOnly is there to make sure the ExecFormDelete sees that this is a group of lines and one of those sub-lines is in a read-only mode. This is supposed to trigger a password prompt.
I am also passing the object that will ask for a password to the ExecFormDelete function. These 2 parameters are based on interfaces which is guaranteeing that whatever objects I pass in those parameters will have the same methods. The live version will pass objects that actually check the database and if the group lines are read only, it will use a method that actually displays a prompt to the user and will require them to enter a password.
The whole point of this test is to make sure a password prompt will be displayed if the Concrete Group lines are read-only. So now we FINALLY reach the Assert:
'Assert:
Assert.AreEqual CLng(1), PwCount.Counter
This is checking to make sure the the test Password object I sent got called one time during this process. If so, then the test passes. If not the test fails and there is something broken in my code.
You can see from the large footprint of this test code that it is not necessarily easy or simple to concoct tests for existing code. Because I’m testing the internal behaviors of forms, I have to figure out which function needed the new code, then determine how to set up the conditions for the test that would mirror the conditions in the live version of the form. And my job still wasn’t complete because I had to then figure out how to determine that a password would be asked for in the test without actually asking for the password during the test.
This may seem like a lot of unneccessary work. In my opinion though, this is going to help me moving forward. I’ve already created an interface that can be used now anyplace a password is requested where I can sense whether that is happening. I would have to update the code in those spots to use that interface, but I’ve done the main work necessary which is creating the interface and a live and test object I can use interchangeably. This is also pushing me to consider my design and how I can work to decouple the form code from the business logic in a way that is interchangeable into other mediums. It would allow me to input some of the information through other sources like a text file of commands instead of a form, using a command line ui, or even a web ui without changing the objects responsible for executing the business logic.
Decoupling ultimately leads me toward a much more flexible design. I’m not anywhere near there yet, but doing this with each change will help me move closer and closer to coding faster and better and having automated tests that will verify the business rules given by the customer.