Trapdoor In The Sun

Alan Shanahan, Technician & Consultant


Leave a comment

Force.com: Visualforce; slightly better field declarations

Soft coded = good. Hard coded = bad. That’s difficult to argue against and I doubt there’s much dissent in any programming community.

Define it once, use it many times; this is part of the reusability principle that applies to code segments and text literals. When you define an input field in a Visualforce page, more often than not it is based on a field that exists somewhere in the database. It makes a whole lot of sense to take as many attributes from the original field definition as possible, so below are some examples of how you might do that.

(1) When defining a field on a page, prefix it with its label. Use this syntax:
$ObjectType.ObjectName__c.Fields.FieldName__c.Label
…where ObjectName__c and FieldName__c should be replaced as appropriate.

(2) Use the field’s own help text by referring to the InlineHelpText attribute, as shown below in this syntax:
$ObjectType.ObjectName__c.Fields.FieldName__c.InlineHelpText
…where ObjectName__c and FieldName__c should be replaced as appropriate.

(3) Limit the field length in HTML during data entry by using this syntax:
$ObjectType.ObjectName__c.Fields.FieldName__c.Length
…where ObjectName__c and FieldName__c should be replaced as appropriate.

<apex:pageBlock id="searchPageBlock">

	<apex:pageBlockSection columns="2"
	 id="searchPageBlockSection" title="Global Search" collapsible="false">

		<apex:pageBlockSectionItem id="searchAirport"
		 helpText="{!$ObjectType.Airport__c.Fields.Airport_Name__c.InlineHelpText}">

			<apex:outputLabel
			 value="{!$ObjectType.Airport__c.Fields.Airport_Name__c.Label}" />
			<apex:inputText
			 value="{!wrkAirportName}"
			 tabIndex="2"
			 id="inpAirportName"
			 maxlength="{!$ObjectType.Airport__c.Fields.Airport_Name__c.Length}"
			 onkeypress="return noenter(event);" />

		</apex:pageBlockSectionItem>
	</apex:pageBlockSection>
</apex:pageBlock>

The above code works well for text input fields. If you need information on numeric or other input field types check out the $ObjectType schema information page.

Advertisement


4 Comments

Force.com: Visualforce programming, position cursor at first input field

When building a custom Visualforce page with input fields, one common requirement is to place the cursor at the first input field on the page. You would think this would be a simple matter and Salesforce itself manages the task quite well. But when the platform’s default behaviour kicks in and overrides your page’s behaviour, it doesn’t always work smoothly. Or if you want to position on an input field that is not a text entry field it doesn’t always slide into place. Or perhaps you wish to show a dialog box where you wish to conditionally position the cursor to a Confirm or Cancel button depending on prior processing.

In this piece, I’m offering a foolproof way to enable your page to behave exactly as you want it to, in an easy, maintainable way. Here is the step by step way to do this.

First, download a copy of the JQuery library from jquery.com. The minimised (compressed) version is just fine.

Now, upload it to your Salesforce org as a Static Resource.

In your Visualforce page, add this line somewhere after the apex:page tag:

<apex:includeScript value="{!$Resource.JQueryJS}" />

…making sure to replace the JQueryJS text with the name you specified during the Static Resource upload step.

The code below assumes that the page is aware of a “mode” or action passed to it, perhaps as a querystring parameter and parsed separately into a variable called wrkAction.


...

	<script language="JavaScript">

	//----------------------------
	// JQuery initialisation
	//----------------------------
	j$=jQuery.noConflict();
	j$(document).ready(function() {
		initialisePage();
	});

	//-------------------------
	function initialisePage() {
	//-------------------------
		positionCursorToFirstField();
	}
	//-------------------------------------
	function positionCursorToFirstField() {
	//-------------------------------------
		if ('new' == '{!wrkAction}' || 'edit' == '{!wrkAction}' || 'copy' == '{!wrkAction}') {
			j$("[id*=':btnConfirm']").focus();
		}
		if ('del' == '{!wrkAction}') {
			j$("[id*=':btnCancel']").focus();
		}
	}
	</script>

The page buttons might be defined later in your page code, as follows:

	<apex:pageBlockButtons id="pbButtons" location="bottom">
		<apex:commandButton id="btnCancel"  value="Cancel"  immediate="true" onclick="return checkCancel();"  />
		<apex:commandButton id="btnConfirm" value="Confirm" immediate="true" onclick="return checkConfirm();" />
	</apex:pageBlockButtons>

Given that any page element will probably exist in the hierarchy of page elements, the Visualforce page is rendered with the implicit force.com naming applied i.e. the full “pathname” to the element is implied in the name, with colons separating each branch in the tree. For example, an HTML id such as j_id0:j_id1:j_id192:j_id193:j_id196:j_id230:j_id231:j_id232:4:j_id236 would not be unusual.

The above code brings a “best practice” tip to mind – always use page-unique ID values for input fields, buttons and all other page components. Specifying the ID tag also ensures that the force.com implicit naming conventions are not applied.

The JQuery code in the positionCursorToFirstField javaScript method above provides a major advantage: you are now effectively freed from having to worry about the naming hierarchy. The JQuery selector finds the field ending with a colon character followed by the specified unique fieldname. This also means you can move it around on the page and within the page element hierarchy (DOM) and not have to worry about this code failing or needing to be modified.

There’s just one more piece of code needed to ensure the force.com standard processing doesn’t happen i.e. that the platform itself doesn’t try to be too clever and preempt what you’re trying to do. This code masks the effect of the standard “first input field positioning” processing:

	<script language="JavaScript">

	//--------------------------------------------------------------------------------------------------------------
	// This empty function overrides the SFDC standard function, and enables us to control initial field positioning
	//--------------------------------------------------------------------------------------------------------------
	function setFocusOnLoad() {
	}

	</script>


3 Comments

Force.com: Apex Styleguide, Part 5

Click for Part 1, Part 2, Part 3, Part 4 in this series.

Arrays or Lists?

With this post I am, strictly speaking, stepping outside of the pure topic of style. It’s one of those grey areas.

It’s that whole idea of whether to use Array or List data structures in your code. If, like me, you come from a background of programming languages where the first element of an array is element # 1, rather than element # 0, then you may also find List structures a little more intuitive because you usually don’t have to worry about element numbers when you iterate.

From the Apex language viewpoint, arrays and lists are interchangeable; you can declare a list and treat it as an array and vice-versa. Try it yourself if you don’t believe me:

public class BlogStyleGuide5 {

  // -----------------------------------------------------
  // The first method declares and populates an Array,
  // then iterates through it as a List.
  // -----------------------------------------------------
  void iterateOLI1() {

    OpportunityLineItem[] arrOLI = [
      SELECT Id, Quantity
      FROM OpportunityLineItem
    ];

    for (OpportunityLineItem iOLI : arrOLI) {
      Id      wrkId  = iOLI.Id;
      Decimal wrkQty = iOLI.Quantity;
      // Do something
    }

  }

  // -----------------------------------------------------
  // The second method declares and populates a List,
  // then iterates through it as an Array.
  // -----------------------------------------------------
  void iterateOLI2() {

    List<OpportunityLineItem> lstOLI = [
      SELECT Id, Quantity
      FROM OpportunityLineItem
    ];

    for (
      Integer i = 0;
      i < lstOLI.size();
      i++
    ) {
      Id      wrkId  = lstOLI[i].Id;
      Decimal wrkQty = lstOLI[i].Quantity;
      // Do something
    }

  }

}

If you were to adopt my preference for List structures rather than Arrays, you might end up having to re-code. That’s why I mentioned that this topic steps a little outside the realm of style. Therefore, please use care if you take this route. Ensure you test your changes thoroughly according to standard “good practice”.

Any comments on the above?


3 Comments

Force.com: Apex Styleguide, Part 4

Click here for Part 1 of this series.
Click here for Part 2 of this series.
Click here for Part 3 of this series.

This one is a little easier on the brain.

From time to time, you will come across a scenario where one structure will need to be copied to another, and there’s no option but to do it “the hard way”, as in the example below. But do you want it to look good?

// Copy temporary record to database object structure
if (copyToRecord) {
  recordObject.Name = tempObject.Name;
  recordObject.Custom_Field_String_13 = tempObject.Custom_Field_String_13
  recordObject.Address_Line_1__c = tempObject.Address_Line_1__c;
  recordObject.Address_Line_2__c = tempObject.Name;
  recordObject.City__c = tempObject.Name;
  recordObject.Country_Code__c = tempObject.Name;
  recordObject.Postcode__c = tempObject.Name;
  recordObject.Contact_1_First_Name__c = tempObject.Name;
  recordObject.Contact_1_Last_Name__c = tempObject.Name;
  recordObject.Contact_2_First_Name__c = tempObject.Name;
  recordObject.Contact_2_Last_Name__c = tempObject.Name;
}

Figure 1, above, shows the raw code as many people would write it. Nothing wrong with that.

// Copy temporary record to database object structure
if (copyToRecord) {
  recordObject.Name                    = tempObject.Name;
  recordObject.Custom_Field_String_13  = tempObject.Custom_Field_String_13
  recordObject.Address_Line_1__c       = tempObject.Address_Line_1__c;
  recordObject.Address_Line_2__c       = tempObject.Address_Line_2__c;
  recordObject.City__c                 = tempObject.City__c;
  recordObject.Country_Code__c         = tempObject.Country_Code__c;
  recordObject.Postcode__c             = tempObject.Postcode__c;
  recordObject.Contact_1_First_Name__c = tempObject.Contact_1_First_Name__c;
  recordObject.Contact_1_Last_Name__c  = tempObject.Contact_1_Last_Name__c;
  recordObject.Contact_2_First_Name__c = tempObject.Contact_2_First_Name__c;
  recordObject.Contact_2_Last_Name__c  = tempObject.Contact_2_Last_Name__c;
}

Figure 2, above, is a “cleaned-up”, column-aligned version of the same code. It took very little effort, but suddenly there’s more clarity.

OK, call me petty, but what do you want your code to say about you?


2 Comments

Force.com: Apex Styleguide, Part 3

Click here for Part 1 of this series.
Click here for Part 2 of this series.

Here, I’m going to take a look at the condition part of the if statement. In particular, how best to write a complex condition to allow for readability and easy code maintenance. Sometimes even just a small number of ANDs and ORs can be easy to write but difficult to untangle later. Add in some brackets for changes in operator priority and the picture becomes even worse. I will refrain from filling up this post with words because I think the example below will provide most of the colour and information I’m trying to impart on the topic.

if (conditionA || (conditionB && conditionC) || (conditionD || conditionE)) {
  doSomething();
}

Figure 1, above, equates to the following:

if A OR (B AND C) OR (D OR E) then do something

When you substitute the conditions for real-world variables, function/method calls or complex structure sub-fields, the results can be less than legible. But, apply a little indentation and split your conditions up and you suddenly have some clarity.

if (
       conditionA
       ||
       (
           conditionB
           &&
           conditionC
       )
       ||
       (
           conditionD
           ||
           conditionE
       )
   ) {
  doSomething();
}

Figure 2, above, is functionally identical to Figure 1. Do you think it’s more readable? Easier to maintain?

A little tip for those engaged in writing complex Force.com custom formula fields with if statements: try using the same method .


3 Comments

Force.com: Apex Styleguide, Part 2

If you haven’t already seen Part 1 of this series, click here to go to it.

Example 2, if/then/else Statements in Apex:

This article deals with the humble if/then/else statement, specifically the executable part of the statement. As before, I’m writing in Apex code, so there may be minimal syntactical differences between this and similar, related languages e.g. C & variants, Java, etc.

We can start with a simple, common binary use of the if statement:

if (a == b) runSomething();
else runSomethingElse();

Figure 1, above, is a simple case of “if a = b then run something, otherwise run something else“. It looks perfectly fine.

if (a == b)
  runSomething();
else
  runSomethingElse();

Figure 2, above, is almost identical to Figure 1 except that the executable part of the if and else clauses are on separate lines and indented. A little better. More readable, perhaps, and functionally identical.

if (a == b) {runSomething(); } else { runSomeThingElse(); }

Figure 3, above, has expanded on Figure 1. The addition of block braces around the executable sections serve to demarcate the runnable parts of the code.

if (a == b) {runSomething(); andSomethingElse(); } else { runSomeThingElse(); andRunAFourthThing(); }

Figure 4, above, is an example of where the programmer has added some extra method calls into the two executable blocks. This would not be possible for Figure 1 and Figure 2 above without the addition of braces.

if (a == b) {
  runSomething();
}
else {
  runSomethingElse();
}

Figure 5, above, is where we FINALLY come to the version that I’m happy with. The if and else clauses are on their own lines, block braces surround the executable sections of both clauses and indentation completes the picture.

if (a == b) {
  runSomething();
  andRunSomethingElse();
}
else {
  runSomethingElse();
  andRunAFourthThing();
}

Figure 6, above, is a clear illustration of how easy it is to add two method calls without upsetting any of the surrounding lines of code in Figure 5. No braces need to be added because they are already there. The value of this style is that you may wish to add multiple lines within any of the code blocks; this makes it very easy to do and retains code legibility.

As ever, please feel free to post your comments, whether you agree with me or not.


8 Comments

Force.com: Apex Styleguide, Part 1

Style, in any artistic or professional endeavour, is impossible to quantify, define or measure. It’s highly subjective and can often be controversial. So I’m going to push my own opinions in this, the first of several posts on the subject of coding styles. They won’t be complex articles, merely a look at what I think is poorly-written code versus how I believe it would be better presented. There will be no attempt to discuss whether the code in question is inherently “correct”; merely that it looks difficult to read and is hard to maintain. The aim is to turn both of these problems around, thus making the code easy to read and maintain.

In cases where there may be several variants, I will present them, and give a synopsis of how “stylish” I think it is.

Your comments, as always, are very welcome on this topic.

Example 1, SOQL Queries in Apex:

Let’s start with a common code segment whereby a SOQL Query is run and the resultant record set is returned into a list data structure.

List<Custom_Object__c> lstRecords = [select id, field1__c, name, number_field__c from custom_object__c where name like 'Smith%' order by lastname, firstname limit 100];

Figure 1, above, shows how an “undisciplined” programmer might put together a code segment that retrieves a record set from a SOQL query. In an IDE or other editor, this might well appear as a single line and you would have to scroll right to see the full detail.

List<Custom_Object__c> lstRecords = [select id, field1__c, name, number_field__c, an_additional_field__c, and_another__c from custom_object__c where name like 'Smith%' order by lastname, firstname limit 100];

Figure 2, above, would show the result of adding two additional fields to be retrieved by the query. Not very pretty.

List<Custom_Object__c> lstRecords = [
  SELECT
      Id
    , Field1__c
    , Name
    , Number_Field__c
  FROM Custom_Object__c
  WHERE Name LIKE 'Smith%'
  ORDER BY FirstName, LastName
  LIMIT 100
];

Figure 3, above, is how I would put this snippet together. I find this more professional-looking, much easier to read and far easier to modify. The following improvements have been made:

  • the outer data structure is separated from the inner SOQL statement
  • code is indented in a useful way
  • keywords are uppercase for readability
  • query clauses are split onto separate lines
  • fields are also split onto distinct lines and vertically-aligned to enable simple editing
  • field names have been retyped with appropriate letters in uppercase

You may not agree with the word “improvement”; please let me know if you don’t, and the all-important reason why.

List<Custom_Object__c> lstRecords = [
  SELECT
      Id
    , Field1__c
    , Name
    , Number_Field__c
    , An_Additional_Field__c
    , And_Another__c
  FROM Custom_Object__c
  WHERE Name LIKE 'Smith%'
  ORDER BY FirstName, LastName
  LIMIT 100
];

Figure 4, above, shows the result of adding the same two additional fields as in Figure 2. The results speak for themselves.

More code examples to follow soon. Why not follow the blog to get email notification of new posts?

Acronyms used above:
SOQL = Salesforce Object Query Language (a proprietary variant of SQL)
IDE = Integrated Development Environment (e.g. Eclipse, MS Visual Studio, NetBeans, JDeveloper, Xcode, etc.)


Leave a comment

The Transition To Force.com Developer

This is aimed at Java and C# developers who want to move into cloud computing and create demand for their skills.

One thing I regard as vital is to constantly keep an eye on your career progression. My aim is to make myself just that little bit more employable every year. It’s tough to combine this with a busy work schedule, but just because it’s tough doesn’t mean it’s not worthwhile.

The last year has seen huge growth and even bigger projections for the future of Cloud Computing. The trends are all upward, there’s no doubt. Gartner’s recent report states that the areas of “social, mobile, big data and cloud” will experience growth as they are championed by new IT leaders. Forrester predicts patterns of growth that are also encouraging. Most pundits and industry commentators agree and many of the big players are positioning themselves along these lines.

Along with explosive sales in mobile and tablet devices, it’s clear that mobile applications (particularly those that are cloud-based) will feature strongly in a global IT context for some time to come.

Add social media to the mix and it’s clear that Salesforce.com (with its Force.com platform) has its bases well and truly covered. They’ve got Chatter; Facebook and Twitter integration is covered; Touch is their technology stack for mobile; mobile apps proliferate.

And at their heart SFDC are “Cloud”. They are pioneers in the arena, that’s an undisputed fact.

Getting down to nuts and bolts, if you’re a developer with Java or C# skills, there’s a definite path you can take to sharpen your skills to become a certified Force.com developer. If you have designs on such a career path, my advice would be to follow these lists:

Application & Configuration Skills:

  • Get yourself trained up on the (Sales Force Automation) SFA application and, optionally, get yourself a certification
  • Understand what Account, Contact, Opportunity, Pricebook, Product, Case, Lead are, and what they do
  • Understand how to use Reports & Dashboards
  • Preferably, attend at least one of the Salesforce Administrator courses
  • Learn how to configure custom objects, custom fields, page layouts, field sets, formula fields, validation rules, custom buttons & links
  • Learn about Security, Roles & Profiles, field-level security
  • Get acquainted with Workflow and Approvals, what they are, what they can do
  • Learn how to import data with the Data Import Wizard

Technical & Coding Skills:

  • Sign up for a free SFDC Developer org
  • Learn about coding on the Force.com platform: Apex Triggers & Classes, VisualForce, Components, Custom Settings, Custom Labels, Resources
  • Understand what Force.com Governor Limits are, how to apply them
  • Learn what SFDC API usage limits are and understand how they may affect any application you design
  • Learn the new coding paradigms for bulk processing (a large topic but here’s a useful starter blog post)
  • Understand Lists, Maps, Sets and when to use them to handle batches of data
  • Understand Test Classes & methods
  • Learn about Apex Batch and Scheduled jobs
  • Learn how to use the SFDC SOAP and REST APIs and Workflow Outbound Messaging options
  • Learn how to extend the API with your own custom web services
  • Understand how to process inbound emails in Apex
  • Use Eclipse and the Force.com plugin to maintain Apex & Visualforce code and to examine the database schema
  • Learn how to debug code and troubleshoot using the various tools on the Force.com platform
  • Understand all the places (or “hooks”) where you can plug into the SFDC application to customise it
  • Gain at least a cursory understanding of SControls (deprecated functionality) in case you need to modernise or maintain old applications
  • Understand how to use the Apex Data Loader
  • To help you on your way with the above, here is a link to all SFDC documentation (pay special attention to the Apex and Visualforce reference docs)

You can also take your existing skills with you, and these will be invaluable:

  • Database and application design skills
  • Data Migration skills
  • Data Integration knowledge
  • Service-Oriented Architecture (SOA) and web service knowledge
  • Industry vertical expertise
  • Test-Driven Development (TDD) strategies
  • Source code version control tools and techniques
  • Multi-developer, multi work-stream development
  • Application testing strategies & methods

You can acquire all the training resources you need with little or no cost, but you can also accelerate your ramp-up time by attending SFDC formal training courses.

Anecdotal evidence would suggest that there are at least some Java developers out there who have a reluctance to extend their knowledge into the Force.com arena, primarily because of the more restricted nature of the environment. However, if you take the plunge, the rewards are there for the taking and you’ll join a growing army of technicians with niche skills.

Don’t be too quick to assume, however, that because you can instantly read Apex code (which a Java or C/Whatever coder can), your transition will be instant – there’s a little more to it than that. Most who took that path with that outlook fell foul of platform differences, a.k.a. the famed Governor Limits. But, learning the How, When and Why of these limits will help get you to the next stage in the transition process.

Acronyms used above:

  • SFDC = SalesForceDotCom
  • API = Application Programming Interface
  • SOA = Service-Oriented Architecture
  • SOAP = Simple Object Access Protocol
  • REST = Representational State Transfer
  • TDD = Test-Driven Development


Leave a comment

Force.com: Build Your Own Test Data Creation Suite

Every coded Force.com project needs its unit test suite. The platform has recently adapted its default practice to make it impossible for your test code to see any data in the database, BUT this is a good thing: it forces us to create unit tests that are truly data-independent. This makes them more portable, and safer.

Another practice that is highly recommended and advantageous to adopt is that of “writing once, reusing many times”; as is true for all code. What I’ve done here is to create a generic template that will help you create test data as you need it, from a common library of methods that are adaptable and can be reused. And the paradigm stretches to include bulk creation of data, which is necessary in the constrained world of Apex programming.

This post gives you the ability to add code to your project that will allow you to create, with very little additional effort, a full set of test data for your own methods and classes to play with. And I won’t be holding back; in fact, by the end of this post you will have a fully-described body of code that you can use as the starting point for your own project.

To demonstrate the concept, I’ve created a set of data-creation methods that will generate data for 4 standard Force.com objects: Account, Contact, Opportunity and OpportunityContactRole. Anyone familiar with the SFA application will understand what these objects are and how they are related. It’s important to understand the links between these tables, however:

  • Account is a top-level or parent table.
  • Contact is a child object of Account. There can be zero or more Contact records related to an Account.
  • Opportunity is a child object of Account. There can be zero or more Opportunity records related to an Account.
  • The OpportunityContactRole object is a “junction object”; it links two tables, in this case Contact and Opportunity. It can also store an optional Role for each record.

This link will show you an entity relationship diagram (ERD) that may help to clarify. Note, also, that I use the terms “object” and “table” interchangeably. For the purposes of this blog post, it will not be necessary to make any distinction and you can think of them as the same thing.

I’ll walk you through the code now. There will be links to the full class and its own test class at the end of the post.

First, we define the class and provide some high-level comments.

public class MyTestDataSuite {
	//================================================================================
	// This class contains a basic blueprint for a generic test data creation suite.
	// Clone and adapt the samples for your own custom objects and fields.
	// Ensure you complement the suite with comprehensive unit tests.
	//================================================================================

Below, I’ve defined some text constants used for the creation of test data. I’ve tried to use some data that is not likely to exist in the database, but as long as you’re not using the IsTest(SeeAllData=true) annotation and are using version 24 of the API or higher, there will be no conflict with existing data. Click here for more information.

	//--------------------------------------------------------------------------------
	// Definition of constants
	//--------------------------------------------------------------------------------
	public static final String ACCOUNT_NAME_PREFIX     = 'TEST ACCOUNT XXXX';
	public static final String CONTACT_NAME_PREFIX1    = 'FIRSTZZZZ';
	public static final String CONTACT_NAME_PREFIX2    = 'LASTZZZZ';
	public static final String OPPORTUNITY_NAME_PREFIX = 'OPPTYZZZZ';

	public static final String CONTACT_SIZE_MISMATCH =
		'Class/Method MyTestDataSuite/createTestContactData:' +
		' Number of Contact records requested does not match related Account map size.';
	public static final String OPPORTUNITY_SIZE_MISMATCH =
		'Class/Method MyTestDataSuite/createTestOpportunityData:' +
		' Number of Opportunity records requested does not match related Account map size.';
	public static final String OCR_SIZE_MISMATCH1 =
		'Class/Method MyTestDataSuite/createTestOCRData:' +
		' Number of OpportunityContactRole records requested does not match related Opportunity map size.';
	public static final String OCR_SIZE_MISMATCH2 =
		'Class/Method MyTestDataSuite/createTestOCRData:' +
		' Number of OpportunityContactRole records requested does not match related Contact map size.';

In the next short section, I’ve simply defined an Exception class to be used by the data creation methods, primarily where unrecoverable errors occur.

	//--------------------------------------------------------------------------------
	// Internal Exception class
	//--------------------------------------------------------------------------------
	public class DataSuiteException extends Exception {}

Just below, we’re finally getting into the real meat of the post. After the initial bounds checking, this code iterates through an integer loop, creating an Account record with incrementing names; they are then added to the return map, using the integer as a map key.

	//--------------------------------------------------------------------------------
	// This is where the batch of Account records are created.
	//--------------------------------------------------------------------------------
	public static Map<Integer,Account> createTestAccountData(Integer numAccs) {

		// Carry out some batch size bounds checking
		if (numAccs > 200) numAccs = 200;
		if (numAccs < 0)   numAccs = 0;

		// Initialise any initial, incrementing or unique values here
		String  wrkAccNamePrefix = ACCOUNT_NAME_PREFIX;
		Integer wrkAccountNumber = 200001;

		// Create the requested number of Account records
        Map<Integer,Account> wrkMapAccs = new Map<Integer,Account>();
        for (Integer iX = 1; iX<=numAccs; iX++) {
			Account wrkAcc = createTestAccountRecord(
				 wrkAccountNumber
				,wrkAccNamePrefix
			);
			wrkMapAccs.put(iX,wrkAcc);
			wrkAccountNumber++;
        }
		return wrkMapAccs;
    }

It’s important to note that I’ve broken out the creation of each individual record into a method in its own right. This makes it easier to read, and easier to maintain. If you wish to populate the values of any other object fields, this is the place to do it.

	//--------------------------------------------------------------------------------
	// Creation of each Account record takes place here
	//--------------------------------------------------------------------------------
	static Account createTestAccountRecord(Integer accNum, String accPfx) {

		Account wrkAccount = new Account(
			 Name                     = accPfx + ' ' + String.valueOf(accNum)
			,Custom_Account_Number__c = accNum
		);
		return wrkAccount;

	}

Below, I’ve created a similar data creation method for Contact, but note the addition of the mapInt2Account map structure; this allows the Contact to be linked to its parent Account record (on a 1:1 basis), by populating the AccountId lookup field with the parent Id value. The bounds checking is a little more involved here; I’m checking that the request to create N Contact records is accompanied by N Account (parent) records to “attach” them to.

 	//--------------------------------------------------------------------------------
	// This is where the batch of Contact records are created.
	//--------------------------------------------------------------------------------
	 public static Map<Integer,Contact> createTestContactData(Integer numCons, Map<Integer,Account> mapInt2Account) {

		// Carry out some batch size bounds checking
		if (numCons > 200) numCons = 200;
		if (numCons < 0)   numCons = 0;
		if (numCons != mapInt2Account.size()) {
			throw new MyTestDataSuite.DataSuiteException(CONTACT_SIZE_MISMATCH);
		}

		// Initialise any initial, incrementing or unique values here
		String  wrkConNamePrefix1 = CONTACT_NAME_PREFIX1;
		String  wrkConNamePrefix2 = CONTACT_NAME_PREFIX2;
		Integer wrkContactNumber  = 200001;

		// Create the requested number of Contact records
        Map<Integer,Contact> wrkMapCons = new Map<Integer,Contact>();
        for (Integer iX = 1; iX<=numCons; iX++) {
			Contact wrkCon = createTestContactRecord(
				 wrkContactNumber
				,mapInt2Account.get(iX).Id
				,wrkConNamePrefix1
				,wrkConNamePrefix2
			);
			wrkMapCons.put(iX,wrkCon);
			wrkContactNumber++;
        }
		return wrkMapCons;
    }

	//--------------------------------------------------------------------------------
	// Creation of each Contact record takes place here
	//--------------------------------------------------------------------------------
	static Contact createTestContactRecord(Integer conNum, Id accId, String conPfx1, String conPfx2) {

		Contact wrkContact = new Contact(
			 AccountId     = accId
			,FirstName     = conPfx1 + ' ' + String.valueOf(conNum)
			,LastName      = conPfx2 + ' ' + String.valueOf(conNum)
		);
		return wrkContact;

	}

The creation of Opportunity records is similar to that of Contacts, with it being related to the Account object by way of a lookup field.

 	//--------------------------------------------------------------------------------
	// This is where the batch of Opportunity records are created.
	//--------------------------------------------------------------------------------
	 public static Map<Integer,Opportunity> createTestOpportunityData(
	 	 Integer numOpps
	 	,Map<Integer,Account> mapInt2Account
	 	,String strStageName
	 	,Date datCloseDate
	 ) {

		// Carry out some batch size bounds checking
		if (numOpps > 200) numOpps = 200;
		if (numOpps < 0)   numOpps = 0;
		if (numOpps != mapInt2Account.size()) {
			throw new MyTestDataSuite.DataSuiteException(OPPORTUNITY_SIZE_MISMATCH);
		}

		// Initialise any initial, incrementing or unique values here
		String  wrkOppNamePrefix     = OPPORTUNITY_NAME_PREFIX;
		Integer wrkOpportunityNumber = 200001;

		// Create the requested number of Opportunity records
        Map<Integer,Opportunity> wrkMapOpps = new Map<Integer,Opportunity>();
        for (Integer iX = 1; iX<=numOpps; iX++) {
			Opportunity wrkOpp = createTestOpportunityRecord(
				 wrkOpportunityNumber
				,mapInt2Account.get(iX).Id
				,wrkOppNamePrefix
				,strStageName
				,datCloseDate
			);
			wrkMapOpps.put(iX,wrkOpp);
			wrkOpportunityNumber++;
        }
		return wrkMapOpps;
    }

	//--------------------------------------------------------------------------------
	// Creation of each Opportunity record takes place here
	//--------------------------------------------------------------------------------
	static Opportunity createTestOpportunityRecord(Integer oppNum, Id accId, String oppPfx, String stageName, Date closeDate) {

		Opportunity wrkOpportunity = new Opportunity(
			 AccountId     = accId
			,Name          = oppPfx + ' ' + String.valueOf(oppNum)
			,StageName     = stageName
			,CloseDate     = closeDate
		);
		return wrkOpportunity;

	}

The creation of OpportunityContactRole records, below, is simple, in that there are only a small number of fields to be populated for this to make any sense. But it’s slight more tricky because it needs to be aware of both the Contact and Opportunity maps created earlier. It’s all about dependencies and ensuring they are maintained correctly.

 	//--------------------------------------------------------------------------------
	// This is where the batch of Opportunity Contact Role records are created.
	//--------------------------------------------------------------------------------
	 public static Map<Integer,OpportunityContactRole> createTestOCRData(
	 	 Integer numOCRs
	 	,Map<Integer,Opportunity> mapInt2Opportunity
	 	,Map<Integer,Contact> mapInt2Contact
	 	,String strRole
	 ) {

		// Carry out some batch size bounds checking
		if (numOCRs > 200) numOCRs = 200;
		if (numOCRs < 0)   numOCRs = 0;
		if (numOCRs != mapInt2Opportunity.size()) {
			throw new MyTestDataSuite.DataSuiteException(OCR_SIZE_MISMATCH1);
		}
		if (numOCRs != mapInt2Contact.size()) {
			throw new MyTestDataSuite.DataSuiteException(OCR_SIZE_MISMATCH2);
		}

		// Initialise any initial, incrementing or unique values here (commented lines, below, left in for clarity)
		//String  wrkOCRNamePrefix     = 'OPPTYZZZZ';
		//Integer wrkOCRNumber         = 200001;

		// Create the requested number of OpportunityContactRole records
        Map<Integer,OpportunityContactRole> wrkMapOCRs = new Map<Integer,OpportunityContactRole>();
        for (Integer iX = 1; iX<=numOCRs; iX++) {
			OpportunityContactRole wrkOCR = createTestOCRRecord(
				 mapInt2Contact.get(iX).Id
				,mapInt2Opportunity.get(iX).Id
				,strRole
			);
			wrkMapOCRs.put(iX,wrkOCR);
			//wrkOCRNumber++;
        }
		return wrkMapOCRs;
    }

Here, you can see that there are two ID fields to be populated. The Role and IsPrimary fields are optional, so make whatever changes are appropriate to your needs here.

	//--------------------------------------------------------------------------------
	// Creation of each OpportunityContactRole record takes place here
	//--------------------------------------------------------------------------------
	static OpportunityContactRole createTestOCRRecord(Id conId, Id oppId, String ocrRole) {

		OpportunityContactRole wrkOCR = new OpportunityContactRole(
			 ContactId     = conId
			,OpportunityId = oppId
			,Role          = ocrRole
			,IsPrimary     = TRUE
		);
		return wrkOCR;

	}

The class wouldn’t be complete without a closing backet.

}

I’ve included the full source code for the above class here, along with its test class. I don’t claim the test class is complete; you may wish to consider adding some more unit tests to make it complete, especially around edge test cases.

The full MyTestDataSuite class:

public class MyTestDataSuite {
	//================================================================================
	// This class contains a basic blueprint for a generic test data creation suite.
	// Clone and adapt the samples for your own custom objects and fields.
	// Ensure you complement the suite with comprehensive unit tests.
	//================================================================================

	//--------------------------------------------------------------------------------
	// Definition of constants
	//--------------------------------------------------------------------------------
	public static final String ACCOUNT_NAME_PREFIX     = 'TEST ACCOUNT XXXX';
	public static final String CONTACT_NAME_PREFIX1    = 'FIRSTZZZZ';
	public static final String CONTACT_NAME_PREFIX2    = 'LASTZZZZ';
	public static final String OPPORTUNITY_NAME_PREFIX = 'OPPTYZZZZ';

	public static final String CONTACT_SIZE_MISMATCH =
		'Class/Method MyTestDataSuite/createTestContactData:' +
		' Number of Contact records requested does not match related Account map size.';
	public static final String OPPORTUNITY_SIZE_MISMATCH =
		'Class/Method MyTestDataSuite/createTestOpportunityData:' +
		' Number of Opportunity records requested does not match related Account map size.';
	public static final String OCR_SIZE_MISMATCH1 =
		'Class/Method MyTestDataSuite/createTestOCRData:' +
		' Number of OpportunityContactRole records requested does not match related Opportunity map size.';
	public static final String OCR_SIZE_MISMATCH2 =
		'Class/Method MyTestDataSuite/createTestOCRData:' +
		' Number of OpportunityContactRole records requested does not match related Contact map size.';
	
	//--------------------------------------------------------------------------------
	// Internal Exception class
	//--------------------------------------------------------------------------------
	public class DataSuiteException extends Exception {}


	//--------------------------------------------------------------------------------
	// This is where the batch of Account records are created.
	//--------------------------------------------------------------------------------
	public static Map<Integer,Account> createTestAccountData(Integer numAccs) {

		// Carry out some batch size bounds checking
		if (numAccs > 200) numAccs = 200;
		if (numAccs < 0)   numAccs = 0;

		// Initialise any initial, incrementing or unique values here
		String  wrkAccNamePrefix = ACCOUNT_NAME_PREFIX;
		Integer wrkAccountNumber = 200001;

		// Create the requested number of Account records
        Map<Integer,Account> wrkMapAccs = new Map<Integer,Account>();
        for (Integer iX = 1; iX<=numAccs; iX++) {
			Account wrkAcc = createTestAccountRecord(
				 wrkAccountNumber
				,wrkAccNamePrefix
			);
			wrkMapAccs.put(iX,wrkAcc);
			wrkAccountNumber++;
        }
		return wrkMapAccs;
    }
                                                         

	//--------------------------------------------------------------------------------
	// Creation of each Account record takes place here
	//--------------------------------------------------------------------------------
	static Account createTestAccountRecord(Integer accNum, String accPfx) {

		Account wrkAccount = new Account(
			 Name                     = accPfx + ' ' + String.valueOf(accNum)
			,Custom_Account_Number__c = accNum
		);
		return wrkAccount;

	}

    
 	//--------------------------------------------------------------------------------
	// This is where the batch of Contact records are created.
	//--------------------------------------------------------------------------------
	 public static Map<Integer,Contact> createTestContactData(Integer numCons, Map<Integer,Account> mapInt2Account) {

		// Carry out some batch size bounds checking
		if (numCons > 200) numCons = 200;
		if (numCons < 0)   numCons = 0;
		if (numCons != mapInt2Account.size()) {
			throw new MyTestDataSuite.DataSuiteException(CONTACT_SIZE_MISMATCH);
		}

		// Initialise any initial, incrementing or unique values here
		String  wrkConNamePrefix1 = CONTACT_NAME_PREFIX1;
		String  wrkConNamePrefix2 = CONTACT_NAME_PREFIX2;
		Integer wrkContactNumber  = 200001;

		// Create the requested number of Contact records
        Map<Integer,Contact> wrkMapCons = new Map<Integer,Contact>();
        for (Integer iX = 1; iX<=numCons; iX++) {
			Contact wrkCon = createTestContactRecord(
				 wrkContactNumber
				,mapInt2Account.get(iX).Id
				,wrkConNamePrefix1
				,wrkConNamePrefix2
			);
			wrkMapCons.put(iX,wrkCon);
			wrkContactNumber++;
        }
		return wrkMapCons;
    }
                                                         

	//--------------------------------------------------------------------------------
	// Creation of each Contact record takes place here
	//--------------------------------------------------------------------------------
	static Contact createTestContactRecord(Integer conNum, Id accId, String conPfx1, String conPfx2) {

		Contact wrkContact = new Contact(
			 AccountId     = accId
			,FirstName     = conPfx1 + ' ' + String.valueOf(conNum)
			,LastName      = conPfx2 + ' ' + String.valueOf(conNum)
		);
		return wrkContact;

	}
    
    
 	//--------------------------------------------------------------------------------
	// This is where the batch of Opportunity records are created.
	//--------------------------------------------------------------------------------
	 public static Map<Integer,Opportunity> createTestOpportunityData(
	 	 Integer numOpps
	 	,Map<Integer,Account> mapInt2Account
	 	,String strStageName
	 	,Date datCloseDate
	 ) {

		// Carry out some batch size bounds checking
		if (numOpps > 200) numOpps = 200;
		if (numOpps < 0)   numOpps = 0;
		if (numOpps != mapInt2Account.size()) {
			throw new MyTestDataSuite.DataSuiteException(OPPORTUNITY_SIZE_MISMATCH);
		}

		// Initialise any initial, incrementing or unique values here
		String  wrkOppNamePrefix     = OPPORTUNITY_NAME_PREFIX;
		Integer wrkOpportunityNumber = 200001;

		// Create the requested number of Opportunity records
        Map<Integer,Opportunity> wrkMapOpps = new Map<Integer,Opportunity>();
        for (Integer iX = 1; iX<=numOpps; iX++) {
			Opportunity wrkOpp = createTestOpportunityRecord(
				 wrkOpportunityNumber
				,mapInt2Account.get(iX).Id
				,wrkOppNamePrefix
				,strStageName
				,datCloseDate
			);
			wrkMapOpps.put(iX,wrkOpp);
			wrkOpportunityNumber++;
        }
		return wrkMapOpps;
    }
                                                         

	//--------------------------------------------------------------------------------
	// Creation of each Opportunity record takes place here
	//--------------------------------------------------------------------------------
	static Opportunity createTestOpportunityRecord(Integer oppNum, Id accId, String oppPfx, String stageName, Date closeDate) {

		Opportunity wrkOpportunity = new Opportunity(
			 AccountId     = accId
			,Name          = oppPfx + ' ' + String.valueOf(oppNum)
			,StageName     = stageName
			,CloseDate     = closeDate
		);
		return wrkOpportunity;

	}
    
    
 	//--------------------------------------------------------------------------------
	// This is where the batch of Opportunity Contact Role records are created.
	//--------------------------------------------------------------------------------
	 public static Map<Integer,OpportunityContactRole> createTestOCRData(
	 	 Integer numOCRs
	 	,Map<Integer,Opportunity> mapInt2Opportunity
	 	,Map<Integer,Contact> mapInt2Contact
	 	,String strRole
	 ) {

		// Carry out some batch size bounds checking
		if (numOCRs > 200) numOCRs = 200;
		if (numOCRs < 0)   numOCRs = 0;
		if (numOCRs != mapInt2Opportunity.size()) {
			throw new MyTestDataSuite.DataSuiteException(OCR_SIZE_MISMATCH1);
		}
		if (numOCRs != mapInt2Contact.size()) {
			throw new MyTestDataSuite.DataSuiteException(OCR_SIZE_MISMATCH2);
		}

		// Initialise any initial, incrementing or unique values here (commented lines, below, left in for clarity)
		//String  wrkOCRNamePrefix     = 'OPPTYZZZZ';
		//Integer wrkOCRNumber         = 200001;

		// Create the requested number of OpportunityContactRole records
        Map<Integer,OpportunityContactRole> wrkMapOCRs = new Map<Integer,OpportunityContactRole>();
        for (Integer iX = 1; iX<=numOCRs; iX++) {
			OpportunityContactRole wrkOCR = createTestOCRRecord(
				 mapInt2Contact.get(iX).Id
				,mapInt2Opportunity.get(iX).Id
				,strRole
			);
			wrkMapOCRs.put(iX,wrkOCR);
			//wrkOCRNumber++;
        }
		return wrkMapOCRs;
    }
                                                         

	//--------------------------------------------------------------------------------
	// Creation of each OpportunityContactRole record takes place here
	//--------------------------------------------------------------------------------
	static OpportunityContactRole createTestOCRRecord(Id conId, Id oppId, String ocrRole) {

		OpportunityContactRole wrkOCR = new OpportunityContactRole(
			 ContactId     = conId
			,OpportunityId = oppId
			,Role          = ocrRole
			,IsPrimary     = TRUE
		);
		return wrkOCR;

	}
    
    
     
}

The full MyTestDataSuiteTEST class:

@isTest
private class MyTestDataSuiteTEST {

	//--------------------------------------------------------------------------------
	// Test method for Account batch creation
	//                 Contact batch creation
	//                 Opportunity batch creation
	//                 OpportunityContactRole batch creation
	//--------------------------------------------------------------------------------
    static testMethod void createAccConOppOCR() {

		// Specify the start of a test data creation session
		Test.startTest();

		// Create map of Account records
		Map<Integer,Account> mapInt2Account =
			MyTestDataSuite.createTestAccountData(200);
		insert mapInt2Account.values();

		// Create map of Contact records
		Map<Integer,Contact> mapInt2Contact =
			MyTestDataSuite.createTestContactData(200, mapInt2Account);
		insert mapInt2Contact.values();

		// Create map of Opportunity records
		Map<Integer,Opportunity> mapInt2Opportunity =
			MyTestDataSuite.createTestOpportunityData(200, mapInt2Account, 'Closed Won', System.Today());
		insert mapInt2Opportunity.values();

		// Create map of OpportunityContactRole records
		Map<Integer,OpportunityContactRole> mapInt2OCR =
			MyTestDataSuite.createTestOCRData(200, mapInt2Opportunity, mapInt2Contact, 'Business User');
		insert mapInt2OCR.values();

		// Verify that the right number of map elements have been created
		System.AssertEquals(200,mapInt2Account.size());
		System.AssertEquals(200,mapInt2Contact.size());
		System.AssertEquals(200,mapInt2Opportunity.size());
		System.AssertEquals(200,mapInt2OCR.size());

		// Randomly check that the variable elements of the Account map were created as expected
		System.AssertEquals(MyTestDataSuite.ACCOUNT_NAME_PREFIX + ' ' + '200001', mapInt2Account.get(  1).Name);
		System.AssertEquals(MyTestDataSuite.ACCOUNT_NAME_PREFIX + ' ' + '200033', mapInt2Account.get( 33).Name);
		System.AssertEquals(MyTestDataSuite.ACCOUNT_NAME_PREFIX + ' ' + '200107', mapInt2Account.get(107).Name);
		System.AssertEquals(MyTestDataSuite.ACCOUNT_NAME_PREFIX + ' ' + '200200', mapInt2Account.get(200).Name);

		// Retrieve random set of Contact records from database and check Name concatenation
		List<String> lstFirstNames = new List<String>();
		lstFirstNames.add(MyTestDataSuite.CONTACT_NAME_PREFIX1 + ' ' + '200001');
		lstFirstNames.add(MyTestDataSuite.CONTACT_NAME_PREFIX1 + ' ' + '200015');
		lstFirstNames.add(MyTestDataSuite.CONTACT_NAME_PREFIX1 + ' ' + '200131');
		lstFirstNames.add(MyTestDataSuite.CONTACT_NAME_PREFIX1 + ' ' + '200200');
		List<Contact> lstContact1 = [
			SELECT Id, Name, FirstName, LastName
			FROM Contact
			WHERE FirstName IN :lstFirstNames
		];
		System.AssertEquals(4, lstContact1.size());  // Ensure we have retrieved 4 records from database

		Integer conCount1 = 0;  // Counter for verified Contact records
		for (Contact iCon : lstContact1) {
			if (iCon.FirstName == MyTestDataSuite.CONTACT_NAME_PREFIX1 + ' ' + '200001') {  // Check Contact record 1
				conCount1++;
				System.AssertEquals(iCon.Name,MyTestDataSuite.CONTACT_NAME_PREFIX1 + ' ' + '200001' + ' ' +
				                              MyTestDataSuite.CONTACT_NAME_PREFIX2 + ' ' + '200001');
			}
			if (iCon.FirstName == MyTestDataSuite.CONTACT_NAME_PREFIX1 + ' ' + '200015') {  // Check Contact record 2
				conCount1++;
				System.AssertEquals(iCon.Name,MyTestDataSuite.CONTACT_NAME_PREFIX1 + ' ' + '200015' + ' ' +
				                              MyTestDataSuite.CONTACT_NAME_PREFIX2 + ' ' + '200015');
			}
			if (iCon.FirstName == MyTestDataSuite.CONTACT_NAME_PREFIX1 + ' ' + '200131') {  // Check Contact record 3
				conCount1++;
				System.AssertEquals(iCon.Name,MyTestDataSuite.CONTACT_NAME_PREFIX1 + ' ' + '200131' + ' ' +
				                              MyTestDataSuite.CONTACT_NAME_PREFIX2 + ' ' + '200131');
			}
			if (iCon.FirstName == MyTestDataSuite.CONTACT_NAME_PREFIX1 + ' ' + '200200') {  // Check Contact record 4
				conCount1++;
				System.AssertEquals(iCon.Name,MyTestDataSuite.CONTACT_NAME_PREFIX1 + ' ' + '200200' + ' ' +
				                              MyTestDataSuite.CONTACT_NAME_PREFIX2 + ' ' + '200200');
			}
		}
		System.AssertEquals(4,conCount1);  // There should be 4 verified Contact records




		// Specify the end of a test data creation session
		Test.stopTest();

    }


	//--------------------------------------------------------------------------------
	// Test method for Account & Contact batch creation bounds checking
	//--------------------------------------------------------------------------------
    static testMethod void createAccountBoundsCheck() {

		// Specify the start of a test data creation session
		Test.startTest();

		// ACCOUNT

		// Create map of Account records with -3 elements (0 will be created)
		Map<Integer,Account> mapInt2AccountA =
			MyTestDataSuite.createTestAccountData(-3);
		System.AssertEquals(0, mapInt2AccountA.size());

		// Create map of Account records with 5000 elements (200 will be created)
		Map<Integer,Account> mapInt2AccountB =
			MyTestDataSuite.createTestAccountData(5000);
		System.AssertEquals(200, mapInt2AccountB.size());

		// Specify the end of a test data creation session
		Test.stopTest();

    }

    static testMethod void createContactBoundsCheck() {

		// Specify the start of a test data creation session
		Test.startTest();

		// CONTACT

		// Create map of Account records with 200 elements
		Map<Integer,Account> mapInt2AccountA =
			MyTestDataSuite.createTestAccountData(200);
		System.AssertEquals(200, mapInt2AccountA.size());

		// Create map of Contact records with 1234 elements (200 will be created)
		Map<Integer,Contact> mapInt2ContactA =
			MyTestDataSuite.createTestContactData(1234, mapInt2AccountA);
		System.AssertEquals(200, mapInt2ContactA.size());

		// Create map of Contact records with 10 elements (from 200 Account, exception will be thrown)
		try {
			Map<Integer,Contact> mapInt2ContactB =
				MyTestDataSuite.createTestContactData(10, mapInt2AccountA);
			System.Assert(false);  // This line should not be reached.
		}
		catch (Exception ex1) {
			System.Assert(ex1 instanceOf MyTestDataSuite.DataSuiteException);
			System.AssertEquals(ex1.getMessage(),MyTestDataSuite.CONTACT_SIZE_MISMATCH);
		}

		// Specify the end of a test data creation session
		Test.stopTest();

    }

    static testMethod void createOpportunityBoundsCheck() {

		// Specify the start of a test data creation session
		Test.startTest();

		// OPPORTUNITY

		// Create map of Account records with 200 elements
		Map<Integer,Account> mapInt2AccountA =
			MyTestDataSuite.createTestAccountData(200);
		System.AssertEquals(200, mapInt2AccountA.size());

		// Create map of Opportunity records with 201 elements (200 will be created)
		Map<Integer,Opportunity> mapInt2OpportunityA =
			MyTestDataSuite.createTestOpportunityData(201, mapInt2AccountA, 'Closed Won', System.Today());
		System.AssertEquals(200, mapInt2OpportunityA.size());

		// Create map of Opportunity records with 199 elements (from 200 Accounts, exception will be thrown)
		try {
			Map<Integer,Opportunity> mapInt2OpportunityB =
				MyTestDataSuite.createTestOpportunityData(199, mapInt2AccountA, 'Closed Won', System.Today());
			System.Assert(false);  // This line should not be reached.
		}
		catch (Exception ex1) {
			System.Assert(ex1 instanceOf MyTestDataSuite.DataSuiteException);
			System.AssertEquals(ex1.getMessage(),MyTestDataSuite.OPPORTUNITY_SIZE_MISMATCH);
		}

		// Specify the end of a test data creation session
		Test.stopTest();

    }

    static testMethod void createOCRBoundsCheck() {

		// Specify the start of a test data creation session
		Test.startTest();

		// OPPORTUNITYCONTACTROLE

		// Create map of Account records with 200 elements
		Map<Integer,Account> mapInt2AccountA =
			MyTestDataSuite.createTestAccountData(200);
		System.AssertEquals(200, mapInt2AccountA.size());

		// Create map of Account records with 100 elements
		Map<Integer,Account> mapInt2AccountB =
			MyTestDataSuite.createTestAccountData(100);
		System.AssertEquals(100, mapInt2AccountB.size());

		// Create map of Contact records with 200 elements
		Map<Integer,Contact> mapInt2ContactA =
			MyTestDataSuite.createTestContactData(200, mapInt2AccountA);
		System.AssertEquals(200, mapInt2ContactA.size());

		// Create map of Contact records with 100 elements
		Map<Integer,Contact> mapInt2ContactB =
			MyTestDataSuite.createTestContactData(100, mapInt2AccountB);
		System.AssertEquals(100, mapInt2ContactB.size());

		// Create map of Opportunity records with 200 elements
		Map<Integer,Opportunity> mapInt2OpportunityA =
			MyTestDataSuite.createTestOpportunityData(200, mapInt2AccountA, 'Closed Won', System.Today());
		System.AssertEquals(200, mapInt2OpportunityA.size());

		// Create map of Opportunity records with 100 elements
		Map<Integer,Opportunity> mapInt2OpportunityB =
			MyTestDataSuite.createTestOpportunityData(100, mapInt2AccountB, 'Closed Won', System.Today());
		System.AssertEquals(100, mapInt2OpportunityB.size());

		// Create map of OpportunityContactRole records with 200 elements
		Map<Integer,OpportunityContactRole> mapInt2OCRA =
			MyTestDataSuite.createTestOCRData(200, mapInt2OpportunityA, mapInt2ContactA, 'Business User');
		System.AssertEquals(200, mapInt2OCRA.size());

		// Create map of OpportunityContactRole records with 123 elements (from 100 Opportunity records, exception will be thrown)
		try {
			Map<Integer,OpportunityContactRole> mapInt2OCRB =
				MyTestDataSuite.createTestOCRData(123, mapInt2OpportunityB, mapInt2ContactA, 'Business User');
			System.Assert(false);  // This line should not be reached.
		}
		catch (Exception ex1) {
			System.Assert(ex1 instanceOf MyTestDataSuite.DataSuiteException);
			System.AssertEquals(ex1.getMessage(),MyTestDataSuite.OCR_SIZE_MISMATCH1);
		}

		// Create map of OpportunityContactRole records with 100 elements (from 200 Contact records, exception will be thrown)
		try {
			Map<Integer,OpportunityContactRole> mapInt2OCRC =
				MyTestDataSuite.createTestOCRData(100, mapInt2OpportunityB, mapInt2ContactA, 'Business User');
			System.Assert(false);  // This line should not be reached.
		}
		catch (Exception ex1) {
			System.Assert(ex1 instanceOf MyTestDataSuite.DataSuiteException);
			System.AssertEquals(ex1.getMessage(),MyTestDataSuite.OCR_SIZE_MISMATCH2);
		}

		// Specify the end of a test data creation session
		Test.stopTest();

    }


}

I will leave it to you to attempt to decipher the code in the test class. If there is sufficient demand, I’ll add a post with a descriptive walkthrough. All comments are welcome and encouraged.