EvilDevilCuckoo, Evil Devil Cuckoo, William Ryan, W.G. Ryan, Bill Ryan, Kim Ryan, Search Engine Optimization, Data Mining

 18 Apr 2014 @ 12:34 AM 

Unit Testing MSCRM 2011 – Part II of V

Unit Testing MSCRM 2011 is something that’s often overlooked in MSCRM development.  In Unit Testing MSCRM 2011 – Part I of V, I introduced the basics and background needed to get started with Unit Tests.  The more I’ve thought about it, the more I’ve realized that an entire release pipeline and development schedule should be applied to MSCRM development, just like any other .NET Project or project using another technology would be.  In any case, let’s move on to the next piece.

Review

Right now, we went through getting a Unit Test project in to your solution, and using the  AssemblyInitialize , AssemblyCleanup, ClassInitialize , ClassCleanup , TestInitialize and TestCleanup attributes to seed your data and clean it up afterward.  Remember that one of the fundamental goals we’re trying to achieve is to have a project that can be run in multiple environments as your code progresses from development to release.  Assume that you just finished a sprint (or whatever cycle you use) and are ready to move the code from Development over to Test or QA.  You’d first package up your MSCRM Solution (we’ll cover automated deployment of MSCRM Solutions later in this series) and deploy it first.  This will have all of your schema changes and artifacts that were just built.  We will have to deal with testing Plugins and Workflows in a separate article but don’t worry, we will be covering those even if we’re ignoring them at the moment. Once you’ve published the solution, the ideal situation is to deploy your test project, run it against the new environment, and verify that all of your tests passed.  This would do several things, the least of which is verify that the new solution changes you made effectively ‘took’.

Assumptions

Before proceeding, the code sample makes the following assumptions:

  1. There is a valid CrmConnection which has its ConnectionString property stored in the <connectionStrings> section of the app.config file.  The Name property is CrmMain.
  2. There is a valid OrganizationService instance named OrgServiceInstance that uses the CrmConnection referenced in the previous step.
  3. There have been several Entities loaded from an Excel file (Accounts, Contacts, Opportunities, Terms, States).
  4. The data was loaded in the ClassInitialize method.  For each entity, there is a collection that contains the Guid of each newly created record.  This Guid, along with the entity type name is used in the ClassCleanup method to make sure all newly created records are removed.  This helps to ensure that the environment is left in essentially the same condition that it was in originally.
  5. The TestContext is referenced in a property called CurrentTestContext .  This contains information loaded from Excel.  For illustrative purposes, there are also collections List<Entity> that hold copies of each of the records created from Excel. This is redundant, but it makes it easier to follow and I’ll explain how to use just one of them later on in the article.
  6. The code is written for readability and is not production code.  If you copy and paste it to perform an operation, please take needed steps to make it production ready.
  7. The code uses the so called Late Bound approach in MSCRM.  The examples would work pretty much identically if the early bound or OrganizationServiceContext approach was used.

 

TestMethod

The TestMethodAttribute is what’s used to identify a given method as a Test method that should be managed and executed by MSTest. Without this attribute, you simply have a method.  The class that contains the method must be annotated with the TestClass attribute. Additionally, the TestMethod attribute must decorate a method, not an Event, a Property or a Constructor. If you need to replicate this functionality, you can do so with a workaround but it probably isn’t advisable.

It is necessary to have a TestMethod attribute on each method that is participating in the test run.  Optionally, you can use the TestCategory attribute to help define some more information about the Test.  TestCategory isn’t required, but I find it beneficial.  For instance, if I was writing a test to verify a method that Queried a Contact entity, I would use two TestCategory attributes, one for Contact and one for Query.  The following test verifies that a QueryExpression to identify a contact based on the Social Security number works.  In practice, this code block would likely be part of another class and we’d simply call the method. For the sake of clarity, I’m showing the code itself.

[TestMethod]
[TestCategory("Contact")]
[TestCategory("Query")]
public void GetContactIdBySSN()
{
ConditionExpression PrimaryCondition = new ConditionExpression();
PrimaryCondition.AttributeName = "cuckoo_memberssn";
PrimaryCondition.Operator = ConditionOperator.Equal;
PrimaryCondition.Values.Add(CurrentTestContext .Properties["ContactSSN"].ToString());

FilterExpression PrimaryFilter = new FilterExpression ();
PrimaryFilter.Conditions.Add(PrimaryCondition);

   QueryExpression Query = new QueryExpression ("contact");
Query.ColumnSet.AddColumns("contactid");
Query.Criteria.AddFilter(PrimaryFilter);
// Realworld would have exception handlers
EntityCollection QueryResults = OrgServiceInstance.RetrieveMultiple(Query);
Assert.IsNotNull(QueryResults);

}

So this is a pretty simple example of using a QueryExpression.  It simply defines a query for a contact that searches for the cuckoo_memberssn property matching a value we have stored in the CurrentTestContext property’s ContactSSN property. This value could be hard coded, it could be stored in a configuration file or a settings file, it could be retrieved pretty much anywhere.  However we loaded this information from Excel so it makes sense to have it defined dynamically as we’ve done here.

When this test runs, the method defined with the AssemblyInitialize attribute will run first, then the ClassInitialize, then the TestInitialize .  The corresponding Cleanup methods (AssemblyCleanup, ClassCleanup  & TestCleanup ) will be run at the end of each series.  In this case, we’re simply trying to determine whether or not there’s a Contact record with a value that matches the ‘ContactSSN’. If there’s a match, we assume our logic worked.  To that end, we use the Assert class’ IsNotNull method, passing in the results of the RetrieveMultiple request we made previously. For the pedantic out there, there are probably 25 ways to accomplish the same test and several of them would be better practice programming wise. The point here is to try to illustrate how to test code though, so that’s why RetreiveMultiple is used for instance.

If this test passes, MSTest will indicate a success (aka. ‘Greenlight’), if it fails it will indicate a failure, or if there is no Assert definition stated, it would come back indeterminate.   After this test ran and all the other ones defined in the class run, the ClassCleanup method would run deleting each of the contacts were created.

Data Driven Tests

One could store a list of Social Security Numbers for the Contact and test them one at a time. This would make sense and since the data was just created, they should all match.    We would also want to test a few other scenarios.  For instance, each of the following should be tested:

  1. That SSN parameter values that are null or have a value of String.Empty should throw an ArgumentNullException (or equivalent validation exception).
  2. Assuming the business logic dictates it, sequences of 9 numbers should work.  If there are hyphens that separate the tokens, they should work. Periods should work.  Spaces should work. However if any of these characters appear in an incorrect index, it should probably not work (again, this may or may not correspond with a real world scenario, some companies may not want any formatting characters. Others might just strip out the characters. Hopefully however you get the idea.
  3. Sequences that don’t exist should intentionally fail.

Figure 1-1 shows an Excel worksheet with a list of Social Security numbers.  In practice, these would correspond to values we knew existed (or in the case of negation, ones we were sure did not exist).

Figure 1-1List of Contact Social Security Numbers

Example of using MSTest in conjunction with the TestClass, TestMethod and DataSource attribute

Example of using MSTest in conjunction with the TestClass, TestMethod and DataSource attribute

So for this test, we want to have MSTest automatically walk through each value and run the test for each listed value.  To do that , we add the DataSourceAttribute and set a few values.

  1. The first argument is the Provider type.  You can use other file formats, for instance, comma separated values, in this case I’m using Excel 2007 (there’s a slightly different connection string for later versions of excel but that’s pretty much irrelevant)
  2. Next is the Dsn specification which should contain a value of Excel Files (semi-colon characters are used to delimit the various tokens).  It needs a dbq specification along with the filename. All three of these should be delimited with a semi-colon and are part of the same parameter.
  3. Next you specify the Sheet name.  Note that whatever name is on the sheet is the name you should use here. Excel has some rules about allowable values so to keep it simple, I’d recommend using simple alphanumeric characters without spaces or special characters (even though you can do otherwise).  Make sure however that the sheet name has a dollar sign at the end of it. So if the Excel sheet name was Contact, a value of Contact$ should be used. If the sheet name was Account, a value of Account$ should be used.
  4. The last parameter, the DataAccessMethod is optional. It is an enumerated value that lets you specify either Sequential or Random.

Here’s an example of what a definition looks like:

[TestMethod]
[TestCategory("Contact")]
[TestCategory("Query")]
[DataSource("System.Data.Odbc", "Dsn=Excel Files;dbq=|DataDirectory|C:\\ContactSSNs.xls", "ContactSSN$", DataAccessMethod.Sequential)]
public void GetContactIdBySSN()
{}

When you run this test, the behavior may be somewhat counterintuitive. You might be inclined to think the test would just execute once, like the non data-driven counterparts do. However that’s not the case.  The test will execute for each value it finds in the excel sheet. Figure 1-1 is truncated at the bottom , but there are at total of 24 values.  So this test will be run 24 times using each subsequent value as the source parameter.  You reference the current context using the TestContext class, referencing the DataRow property. Just like you would with a System.Data.DataTable , you reference the individual row with either a column name or an index.  When you look at the implementation, it becomes pretty clear that the scenario is likely implemented using a DataAdatper, calling the Fill method and then using the resulting DataTable to execute the tests with (however that’s neither here nor there). When I get a chance, I’ll open up the library using Telerik’s Just DeCompile and verify that theory. In any case, here’s what the new version of the test would look like:
[TestMethod]
[TestCategory("Contact")]
[TestCategory("Query")]
[DataSource("System.Data.Odbc", "Dsn=Excel Files;dbq=|DataDirectory|C:\\ContactSSNs.xls", "ContactSSN$", DataAccessMethod.Sequential)]
public void GetContactIdBySSN()
{
ConditionExpression PrimaryCondition = new ConditionExpression();
PrimaryCondition.AttributeName = "cuckoo_memberssn";
PrimaryCondition.Operator = ConditionOperator.Equal;
PrimaryCondition.Values.Add(TestContext .CurrentRow["ContactSNN"].ToString());

FilterExpression PrimaryFilter = new FilterExpression();
PrimaryFilter.Conditions.Add(PrimaryCondition);QueryExpression Query = new QueryExpression(“contact”);
Query.ColumnSet.AddColumns(“contactid”);
Query.Criteria.AddFilter(PrimaryFilter);
// Realworld would have exception handlers
EntityCollection QueryResults = OrgServiceInstance.RetrieveMultiple(Query);
Assert.IsNotNull(QueryResults);
}

If each of the SSN’s in the Excel file match a given contact in the Crm instance referenced by with the CrmConnection, then each pass should return a instantiated Entity value. Since it’s not null, the Assertion should pass and viola.  I randomly chose 24 records but it could just as easily be 100 or 1000.  To test the affirmation, it only makes sense to test values that you just added so you know that they are actually there. However you could make sure that the values weren’t valid SSNs and you could verify that the tests fail. You could purposely make sure that the SSN was in an invalid format and use the ExpectedException attribute to make sure that validation was working properly There’s quite a bit you could use to test here. And since all the heavy lifting is done by MSTest, once you get setup in place, the rest of the work is pretty trivial. Running the test 100 times is scarcely less difficult than running it 1000 times.

 

Conclusion:

So far, we’ve built a Test Project and started creating some basic test methods. Using the TestClass and TestMethod attribute, we’ve been able to create a simple test to verify a query for a Social Security Number in a CRM Contact works. We’ve augmented the TestMethod with the TestCategory attribute which allows us to organize and run tests together.  We then started using the DataSource attribute to allow us to run Data Driven Tests.  in this example, we only used the Sequential value of the DataAccessMethod enumeration, but we could just as easily have used the Random value and in many cases, it would be a better choice. In CRM, because tables are so heavily linked together, it’s often necessary to create data in a specific order so that the Guid identifier for the record can be easily recorded and used to build the subsequent records.  Once the data is created (which we argued makes most sense to do in either the AssemblyInitialize , ClassInitialize or TestInitializemethods), accessing it randomly probably makes a lot of sense in most cases.  The ultimate objectives so far is to be able to thoroughly test code blocks and do so in a manner that lets us dynamically determine what we want to test.  By using a data source that’s external (in this case, Excel) we have the ability to change what we test.  Ultimately, this lets us push out a build, run the test suite which creates a good bit of test data, run several tests against it than delete the data.  While this may seem less than spectacular, just think of how long it would take you to create 5 Accounts and 10 total contacts associated to those Accounts.  Microsoft Dynamics CRM is frequently described as “Clicky” – one of the main criticisms I’ve come across with it so creating test data can be time consuming. Sure, you can create scribe jobs or use Excel sheets to import data, but that still leaves you responsible for cleaning up the data and it’s awkward. Instead, you can use TFS Automation to push out a new solution, publish it, run a set of data driven tests that could easily span several thousand tests (affirmative and negative) and then clean itself up. This is a huge benefit and would greatly benefit any development organization. Let’s face it, a lot of CRM development projects either fail or run much longer than they were planned to.  Anything that adds quality and provides a means of measuring quality and progress should be considered.  Seeing how little effort it takes to employ data driven tests, I’d highly recommend it for any project. Additionally, by using Excel or another similar data source, it’s very easy for non-developers to create test plans and test projects which can be run and verified.  Excel is a very well known product so there’s very little learning curve associated with using it.  If you don’t want to unit test, or you want to run tests one at a time, that’s your prerogative. But keep in mind that adding dynamic tests that can test items thousands of times over takes such little extra work, the question isn’t ‘why should we do this’ but ‘why aren’t we?”

In the next piece, we’ll cover automated deployment of MSCRM solutions and then running the unit tests as part of he build.

 

KeyWords: Unit Testing MSCRM 2011 , MSTest,   CrmConnection, ExpectedException , QueryExpression, FilterExpression, OrganizationService, IOrganizationService, EntityCollection, ColumnSet, DataAccessMethod.Sequential, DataAccessMethod.Random, DataSource, TestContext , TestCategory, TestMethod, ArgumentNullException , AssemblyInitialize , AssemblyCleanup, ClassInitialize , ClassCleanup , TestInitialize,  TestCleanup , OrganizationServiceContext , RetrieveMultiple

 

 

 

 

 

Note on Decompilers

As a general note, you may have noticed that I frequently mention Telerik Decompile. I have no affiliation with the company other than having a friend or two that works there.  There are many decompilers on the market, RedGates Reflector being one of the most prominent. I was  a user of Reflector since it first came out and think RedGate is a great company and the current offering of Reflector is still a superb product.  I would recommend either product and use them both.  In any case, looking into the internals is what allows you to often cut through the clutter and figure out how things truly work.  I’d highly recommend you buy a license for one of these two products if you haven’t already.  I’ve also had a great deal of success with Salamander and like it a lot.

 

 

 

Donate Dogecoins: DFxAsJEQenZvj8W8BcvMmMpZVp8DhFUr4a Whats This?

Technorati Tags: , , , , , , , , , , , , , , , , , , , , , ,

Share and Enjoy

  • Facebook
  • Twitter
  • Delicious
  • Digg
  • StumbleUpon
  • Add to favorites
  • Email
  • RSS


 

Responses to this post » (3 Total)

 
  1. Excellent start Mr Ryan, but it seems you are falling back into your old habit. Parts 1 & 2 come out a day after each other, then a few weeks pass and nothing. If you’re going to tell us there’s more coming, follow through. If you’re going to tell us you’re back to blogging regularly, do so. You have tremendous potential as a blogger, but you consistently fail to follow through on your promises which is truly disappointing.

  2. Edward says:

    brilliant post mate

  3. Edward says:

    Great post. As the others said, I wish you’d finish the series.

Tags
Comment Meta:
RSS Feed for comments

 Last 50 Posts
 Back
 Back
Change Theme...
  • Users » 11613
  • Posts/Pages » 125
  • Comments » 415
Change Theme...
  • VoidVoid « Default
  • LifeLife
  • EarthEarth
  • WindWind
  • WaterWater
  • FireFire
  • LightLight

Code Sample Reference



    No Child Pages.

Disclaimer



    No Child Pages.