Using Test Stubs

[Previous] [Next]

The first thing you should do to allow UI developers to collaborate with your facade objects is to make these objects, which are based on the facade class, act like "real" objects. The methods of the class should be able to accept calls, and they should be able to return results. At this stage, they don't need to base their actions on the arguments they receive when called. They could very well return the same information each time they're called, no matter which arguments they're sent. Their mission now is simply to allow GUI developers to make the calls and receive something in return. When you have developed these so-called test stub methods, GUI developers can start creating user interfaces that call them.

Generating Code

It doesn't matter whether you use Rational Rose, which is available for a price from Rational Corporation, or Microsoft Visual Modeler, which is included for free in Microsoft's Visual Studio 6.0 and Visual Basic 6.0 Enterprise Editions. In either case, you can automatically generate Visual Basic code from your design models.

(There are also other code-generating modeling products on the market, but we'll set them aside. This, of course, is because we ourselves use and are therefore more familiar with Rose and Visual Modeler.)

Furthermore, no law says that you must use a modeling tool and generate code from it. Modeling tools are convenient to use, but you don't have to take advantage of them. We do urge you to use them, though, even if they sometimes seem a bit too complicated, because they can help you rapidly design your application and produce a usable prototype. In any case, you can certainly create all your designs and all your code directly in a development product such as Visual Basic.

By the way, Rose and Visual Modeler are completely model compatible with each other, at least if you use the same version of both. Models that you have created in one of them will open in the other, and you'll lose no information in the models as you move them between the products.

You might want to note two differences between Rose and Visual Modeler:

  • Rose, which you'll have to pay for, is much more complete than Visual Modeler, which is yours for free if you own a license for one of the Microsoft Enterprise Editions we mentioned. Visual Modeler is strictly a design tool, whereas Rose is a design tool and an analysis tool. This means, for example, that you can do use case analysis in the Rose product but not in Visual Modeler.
  • At any point in time, the current version of Rose is liable to be newer than the version of Visual Modeler that Microsoft ships.

Designing a component

Before you can generate any code from a model in Rose or Visual Modeler, you must design at least one component. Because you'll be generating code for a facade class, you need an ActiveX DLL component for at least some of the facade classes of your project. Figure 6-1 shows how you can document such a component in the model.

Figure 6-1. An ActiveX DLL component for facade classes used in the maintenance portion of our sample horse racing application.

Assigning classes to the component

You must also assign the facade class to this component. Figure 6-2 shows the component specification for the RaceMaintFacades component. By the check mark beside its name, you can see that the fcdHorseMaint class is already assigned to the component. This class, by the way, is the only class in Figure 6-2 that has Visual Basic as its language. Later we'll assign other facade classes as well to this component, but right now fcdHorseMaint is the only one we've designed.

Figure 6-2. The fcdHorseMaint class is assigned to the RaceMaintFacades component.

Assigning a class to a component is easy. All you have to do is drag the class to the component and then drop it. When you're done, the class is assigned to the component and will be compiled with it. There are other ways to assign a class to a component, but this way is the easiest.

Generating the code skeleton

Until recently, all you could generate from products such as Rose and Visual Modeler was code skeletons. Rose and Visual Modeler didn't generate implementation code, so you had to add that yourself in the integrated development environment (IDE) of your language. But Rational came out with a new version, named Rose 98i, in which the letter i stands for integration. And now Rational has produced a special version named Rose 2000 for Windows 2000. These versions of the Rose product include a very interesting design tool called the Model Assistant. The version we actually use is Rose 2000, and later in this book we'll briefly touch on the code generation options the Model Assistant opens for you. For now, however, we'll stick to the more old-fashioned way of generating only code skeletons.

After we right-clicked on the component and then clicked Update Code, Rose rewarded us with the first step—shown in Figure 6-3—of its Code Update Tool. If you run Visual Modeler 6.0 or an earlier version of Rose, your user interface to code generation is different, but the functionality is the same.

click to view at full size.

Figure 6-3. First step of the Code Update Tool of the Rose 2000 product.

In the next step, which you can see in Figure 6-4, you can confirm or change the assignment of classes to components. You can change assignments in either of two ways:

  • Assigning new classes to existing components
  • Creating new components and assigning classes to them

You can also ask the Code Update Tool to teach you more about components. This is the third option the Code Update Tool makes available, as you can see in Figure 6-4.

click to view at full size.

Figure 6-4. A chance to confirm (or update) the assignment of classes to components before generating any code.

Figure 6-5 shows the last step before the actual generation of code, which allows you to see what you have asked for before you give your final confirmation.

click to view at full size.

Figure 6-5. A chance to confirm (or go back to make changes) before generating code.

When the code is generated, Rose or Visual Modeler might ask you to confirm the deletion of certain modules in the code. Typically, when Rose or Visual Modeler creates a new project in Visual Basic, the project contains a form. In this case, we don't want the form, so we confirm that it should be thrown away.

Finally, you get the confirmation page, shown in Figure 6-6. It tells you which components and which modules have been affected by the code generation, and it allows you to take a closer look at the log that the code generator produced to let you see what really happened.

click to view at full size.

Figure 6-6. Rose reports which components and classes in the Visual Basic project were updated.

The Project in Visual Basic

Now let's take a look at the Visual Basic project to see what Rose generated for us. Figure 6-7 reveals that it produced one project and one class—the RaceMaintFacades project and the fcdHorseMaint class.

Figure 6-7. The Visual Basic project created by Rose.

Rose also created a project folder named Related Document, containing the HorseMaint.mdl model as a document. This, of course, is the Rose model from which we generated the code.

Taking a look at the Properties window for the class module as well (see Figure 6-8), you can see that there's a problem: the Instancing property of the class has been set to Private. This, of course, is no good. You'll have to change that and make the class MultiUse instead.

Figure 6-8. Be careful! The Instancing property of the generated class is set to 1 – Private.

As Figure 6-9 shows you, the change of the Instancing property from Private to MultiUse also makes the MTSTransactionMode and Persistable properties available.

Figure 6-9. The Instancing property of the generated class should be set to 5 – MultiUse, which makes the MTSTransactionMode and Persistable properties visible.

Rose does give you ways to influence the Instancing property. As already mentioned, Rose 98i and Rose 2000 both offer a new view of classes called the Model Assistant. In fact, the Model Assistant is the place where you want to edit all Visual Basic aspects of your classes. Figure 6-10 shows our facade class as displayed by the Model Assistant.1

click to view at full size.

Figure 6-10. The Model Assistant in Rose 2000.

Notice that we've used the Model Assistant to change the Instancing property of the class as well as to add documentation text to it.

The Generated Code

Figure 6-11 shows the kind of code that Rose generates.

click to view at full size.

Figure 6-11. Code skeleton generated from the Rose model. Focus is on the GetEmptyRecordset function.

Of course, this is only part of the code generated for the class. In time, we'll look at and use the rest of it as well. But here are some details you can learn from Figure 6-11:

  • The class documentation from the Model Assistant (Figure 6-10) has been generated as a module-level comment.
  • Rose 2000 has generated the Option Explicit statement, which is good.
  • The method documentation concerning the GetEmptyRecordset method has been turned into a nice comment header for the function definition.
  • The signature of the function has been properly generated.
  • There's a place for your own code.
  • Above the signature of the function, you can see the ModelId, a code that connects the function to its corresponding method in the model. As long as you don't change the ModelId, you can freely edit either the Rose model or your own code module and Rose will make your edits consistent. You can even change the name of the function in Visual Basic or in the model. Next time you update your code from the model or update your model from the code, they'll synchronize with each other again.

Your Own Code

To turn the GetEmptyRecordset function into a test stub, you need to write some code that creates a proper recordset to return to the client. After you create a recordset object, you can choose between two different ways to add fields to it.

The first way is to set up a connection to the database and then send it a SELECT statement that returns no rows, like this one:

SELECT HorseId, HorseName, BredIn, Sex, Birthyear, Trainer FROM Horses WHERE 'A' = 'B'

This statement returns a proper recordset from the database. Since A will never equal B, the returned recordset will always be empty.2 This works fine with Microsoft SQL Server. You have to check that you really get an empty recordset if you're targeting other database servers.

This method, however, has a drawback. The facade layer should be isolated from the database; it should never be in direct contact with it. You can always isolate it later on when the other layers exist, but there's also a good chance you'll forget to do that, leaving redundant code and declarations and possibly even wasting server resources.

Therefore, we'll opt for the second alternative, which is to create the recordset in code, using the Fields.Append method of the recordset.

Creating an empty recordset

Here's the actual code for creating such a recordset:

Public Function GetEmptyRecordset() As Recordset Dim rs As ADOR.Recordset Set rs = CreateObject("ADOR.Recordset") rs.Fields.Append "HorseId", adInteger, 4 rs.Fields.Append "HorseName", adVarChar, 20 rs.Fields.Append "BredIn", adVarChar, 3 rs.Fields.Append "Sex", adVarChar, 1 rs.Fields.Append "Birthyear", adSmallInt, 2 rs.Fields.Append "Trainer", adInteger, 4 Set GetEmptyRecordset = rs End Function

This code is easy enough to create and to understand. To be really sure of getting all the results correctly, we cheated. We created a Standard EXE project and added a Data Environment with a Command object issuing the following SQL statement:

SELECT * FROM HORSES WHERE 'A' = 'B'

Then we added the following code to a command button. Since we planned to throw the project away immediately after using it, we didn't even bother to rename the command button.

Private Sub Command1_Click() Dim rs As Recordset, x As Integer DataEnvironment1.Command1 Set rs = DataEnvironment1.rsCommand1 For x = 0 To rs.Fields.Count - 1 Debug.Print rs.Fields(x).Name Debug.Print rs.Fields(x).Type Debug.Print rs.Fields(x).DefinedSize Next rs.Close End Sub

After running this code, we had a debug window that looked just like the one in Figure 6-12.

Figure 6-12. The result of running the preceding investigating code.

Then it was easy to use the Object Browser to find out that the symbolic name adInteger meant 3 and would be suitable for the HorseId and Trainer fields, that adVarChar meant 200 and would suit the HorseName, BredIn, and Sex fields, and—as you can see in Figure 6-13—that adSmallInt is the symbolic name to use for 2, a constant to use in the Birthyear field.

click to view at full size.

Figure 6-13. The Object Browser reveals that the DataTypeEnum constant named adSmallInt really means 2.

That was it!

In about five minutes we turned the GetEmptyRecordset function into a test stub. As we did that, we created an opportunity for user interface developers to test the programming interface to this part of our application.

The GetHorseList method

Now let's move on to the GetHorseList method. Here's the code for it, as Rose 2000 first generated it:

'The GetHorseList method takes a name pattern and returns a 'recordset containing data about all the horses that have 'names that fit this pattern. 'Arguments: '========= 'strNamePattern : String 'Returns: '======= 'A read-only recordset containing the following fields: '- HorseId '- HorseName ' '##ModelId=37132219003D Public Function GetHorseList (ByVal strNamePattern As String) _ As Recordset '## Your code goes here.... End Function

The mission for this method is to create an ADO recordset and populate it with the IDs and the names of a number of horses. So you must first create such a recordset. You can copy part of the code for recordset creation from the GetEmptyRecordset method and just paste it in. Here's the result:

Public Function GetHorseList (ByVal strNamePattern As String) _ As Recordset Dim rs As ADOR.Recordset Set rs = CreateObject("ADOR.Recordset") rs.Fields.Append "HorseId", adInteger, 4 rs.Fields.Append "HorseName", adVarChar, 20 End Function

Next you'll have to add at least two horses to the recordset. It doesn't really matter which IDs and which names you give to these horses. All you want to do is offer client developers a chance to call your methods. They should also get something in return, something they can use to build a first demo version of the user interface. So here goes:

Public Function GetHorseList (ByVal strNamePattern As String) _ As Recordset Dim rs As ADOR.Recordset Set rs = CreateObject("ADOR.Recordset") rs.Fields.Append "HorseId", adInteger, 4 rs.Fields.Append "HorseName", adVarChar, 20  rs.Open rs.AddNew rs!HorseId = 10 rs!HorseName = "Nijinsky II" rs.AddNew rs!HorseId = 20 rs!HorseName = "Nearco" Set GetHorseList = rs End Function

The last thing you must do in this function, as you can see from the preceding code segment, is have the function return the recordset it created.

Of course, the function always returns the same set of horses, no matter which search pattern the client uses. That's OK, however, because the purpose of the test stub code is to have the function accept calls and return something to create an impression of being alive.

The remaining methods

Let's just display the test stub code for the remaining methods, without too many comments. The first one, GetHorseById, naturally resembles the GetHorseList method:

'The GetHorseById method takes a horse ID and returns a 'recordset containing data about that horse only. 'Arguments: '========= 'lngId : Long 'Returns: '======= 'A Static Recordset containing the following fields: '- HorseId '- HorseName '- BredIn (country where born) '- Sex '- Birthyear '- Trainer (ID of current trainer of the horse) '##ModelId=3713356A02BE Public Function GetHorseById(lngId As Long) As Recordset Dim rs as ADOR.Recordset Set rs = CreateObject("ADOR.Recordset") rs.Fields.Append "HorseId", adInteger, 4 rs.Fields.Append "HorseName", adVarChar, 20 rs.Fields.Append "BredIn", adVarChar, 3 rs.Fields.Append "Sex", adVarChar, 1 rs.Fields.Append "Birthyear", adSmallInt, 2 rs.Fields.Append "Trainer", adInteger, 4 rs.Open rs.AddNew rs!HorseId = lngId rs!BredIn = "USA" rs!Sex = "h" rs!Birthyear = 1988 rs!Trainer = 16 Select Case lngId Case 10 rs!HorseName = "Nijinsky II" Case 20 rs!HorseName = "Nearco" Case Else rs!HorseName = "Somebody else" End Select Set GetHorseById = rs End Function

We've been a little extravagant here. If the client gets its set of horses from the GetHorseList method, we allow it to select one of the horses and get information about it. Following this procedure ensures that the name the client displays corresponds to the name of the horse selected.

As you can see from the Select Case statement in the code, we've also added a third choice for cases in which the client somehow manages to select a horse that doesn't actually exist. The third option, of course, is a safeguard. A server component must be able to handle incorrect as well as correct input values, and it must be able to separate the right ones from the wrong ones.

When the GetHorseById method returns data on a horse selected by the user, only the Name field will vary, based on which HorseId was sent with the call. The rest of the information returned through the recordset, meaning the BredIn, Sex, Birthyear, and Trainer fields, will always be the same.

You can, if you like, make the SaveHorse method very lightweight. The following example shows how the SaveHorse method raises an error stating that the record can't be saved:

'The SaveHorse method takes a recordset containing data about 'one horse only. It sends this recordset to the database 'for persistent storage. 'Arguments: '========= 'rs : Recordset 'Returns: '======= 'Nothing '##ModelId=3713460A023C Public Sub SaveHorse(rs As Recordset)  Err.Raise Number:=vbObjectError, _ Source:="fcdHorseMaint.SaveHorse", _ Description:= _   "Can't save the record because I'm only a test stub." End Sub

Or you can make the same method report that the record is being saved even though it isn't:

'The SaveHorse method takes a recordset containing data about 'one horse only. It sends this recordset to the database 'for persistent storage. 'Arguments: '========= 'rs : Recordset 'Returns: '======= 'Nothing '##ModelId=3713460A023C Public Sub SaveHorse(rs As Recordset)  Err.Raise Number:=vbObjectError, _ Source:="fcdHorseMaint.SaveHorse", _ Description:= "Saving " & rs!HorseName End Sub

The DeleteHorse method can follow the same pattern:

'The DeleteHorse method takes a horse ID that represents a 'horse that should be deleted. 'Arguments: '========= 'lngID : Long 'Returns: '======= 'Nothing 'NOTE: If, according to a certain business rule, the deletion 'is changed to a deactivation, the DeleteHorse method will 'raise an error to inform its client of the change. '##ModelId=3714505800C9 Public Sub DeleteHorse(lngId As Long) Err.Raise Number:=vbObjectError, _ Source:="fcdHorseMaint.SaveHorse", _ Description:= "Deleting " & rs!HorseName End Sub

We Kept It Clean

Now we've turned our facade class into a test stub. It's capable of answering any call from clients, and it acts as if it were a real class. It doesn't give you proper answers, but it gives its fake answers in the proper form.

GUI developers can now use the class from their client applications. They can create user interfaces that call the facade, and they'll be able to judge whether the interface of the server application is well made. That GUI developers can proceed from only this much of your development work also means that they can start their productive work earlier.

And we have kept it clean! Our class does return ADO recordsets, but it's not connected to the database. Therefore, there's nothing you have to clean up when converting this test stub class to a real one.

In Chapters 20, "An XML Overview," and 21, "XML with ADO 2.5 for Our Components," we'll also show you how to replace the ADO recordset with an XML data set. Times are changing right now. If at present you develop for an environment with Windows NT 4.0 and MTS, you'll probably want to keep and transport data using ADO recordsets. If, on the other hand, you're already using Windows 2000 and COM+, chances are you'd prefer to use XML for your data transportation and temporary keeping of application state.



Designing for scalability with Microsoft Windows DNA
Designing for Scalability with Microsoft Windows DNA (DV-MPS Designing)
ISBN: 0735609683
EAN: 2147483647
Year: 2000
Pages: 133

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net