Testing a workflow inside a Smartform

So I have posted a few articles on testing in K2, we have looked at Unit Testing,  Testing Smartforms and then just recently more examples of testing a workflow.  After writing that article I realised that there was something missing. What about building a workflow testing tool inside of a Smartform. As it all well and good that we can write unit tests for testing a workflow, but that relies on the workflow developer being able to write code and being that the whole idea of K2 is to build low code , no code solutions. Then there should be away of doing it without the developer having to write code.

There are a number of tools out in the market and they do absolutely fantastic job of testing workflows without having to write code. But I wanted to see if it could be done inside a Smartform.

So my challenge is to build a Smartform App that would test a workflow, with out the tester having to write any code.

The app had to do the following

  1. Start any workflow and check that it has started
  2. Check that tasks have been created and the correct number
  3. Action a task
  4. Checking the task has been completed
  5. Check that the workflow has finished
  6. Check the workflow has gone down the direct path.

The app should all be in Smartforms and should be easy to use.

So lets see how I got on.  So i am going to start with the finished tool and then i will take us through it.

Testing Dashboard

The dashboard shows all the current tests that have been performed and shows the overall percentage of pass and failures.

dashboard

From here you can also click on ‘Create Test’ button to create a new test. Selecting an existing test and clicking on ‘Run Test’ will  run an existing test. Finally double clicking on a test will open up the details about the test.

Test Details

So the test details shows the following information

test details

  1. Workflow being tested
  2. If has already been tested
  3. When it was tested
  4. Overall Pass or Fail
  5. What percentage passed and failed
  6. What was tested and whether each test passed or failed.
  7. It also shows any other tests relating to the workflow being tested.

From the test details you also have the ability to retest the test as well.

Creating a new test

From the dashboard click on ‘Create Test’ button.

test builder.PNG

  1. In the new test form, give the test a name
  2. Select a workflow that you are going to test. Once selected you will then be able to see all the activities of that workflow beneath.test-builder1
  3. Select the activities that the workflow will go throughtest-builder2
  4. The last section is where we build up the actual the tests and what results we expect from those tests.test-builder3

Building a simple test script

The workflow we have selected is a very simple one. It has the following activities

  1. Setup
  2. Task
  3. Approve
  4. Reject
  5. End

test-workflow

Task is where there is a client event which has one slot and goes to the originator.

Now that we have got the basic test details and what route we expect the test to go. We can now build are test script.

  1. Select ‘Start Process’ from the test type drop down list
  2. You will notice that the other text boxes in the row, will be pre populated with information and some will be disabled depending on what information the particular test type requires.
  3. Now we choose comparison sign we are going to use it can be either
    1.  =
    2. >
    3. >=
    4. <
    5. <=
  4. We are going to choose >
  5. In the expected result / comparison box lets enter 0, this is because if workflow starts successfully we expect a value greater than 0.
  6. So ‘Start Process’, needs to know the ‘Folio’ to give the workflow, you can either enter in something. Or if you leave it blank it will take the name we have given earlier to the test.
  7. The next parameter we need to give the test type is the process name. You can either copy the full workflow name and paste into this box or leave it and it will automatically get the workflow name.
  8. The next box is called milliseconds, you can either leave it at 0. Or you can enter in a value. This is useful as it may be your workflow is taking a while to perform an event. So you can enter in a delay to the tests to compensate for this.
  9.  Click on ‘Add’ again , we now what to check the status of the workflow, to make sure that once we started it. It is now active.
  10. So select ‘Get Workflow Status’ from the drop down list
  11. Choose ‘=’ from Sign
  12. Enter ‘Active’ in expected result
  13. We don’t need to enter in anything else
  14. Click on ‘Add’
  15. We now want to check to see if has generated some tasks
  16. Select ‘Get Task Count’
  17. Select ‘=’ in Sign
  18. Expected Result should be 1, as we know it’s only one slot and going to the originator.
  19. ProcessInstanceId, leave that how it is, as the testing framework will replace that with the actual process instance id as run time of the test
  20. Process name either enter in the full name or leave it and it will get automatically populated
  21. Next we can action the task
  22. Select ‘=’ for the sign
  23. In the expected result box enter ‘True’
  24. Where it’s says ‘Action’ we need to enter in the action we expect the test to use. So lets enter ‘Approve’
  25. Where it says ‘Serial Number’ we leave that and like the process instance id, it will be populated by the testing framework at run time.
  26. For Destination user, enter in the destination user that the task is supposed to be for.
  27. When this test type runs it will try and action the task based on the information we have given it (Task Action and Destination User) if the test is successful the actual result will be true.
  28. The last test we are going to do is to check that workflow has gone down the correct path and the correct activities were used. The test will compare the path we expect it to be. which was defined in the ‘Workflow Route’ section to the actual path taken.
  29. Select ‘Compare Activities’ from ‘Test Type’
  30. Select ‘=’ from Sign
  31. Enter in ‘True’ in expected result
  32. Leave ProcessInstanceId and Activities how it is
  33. In the Millisecond box enter ‘4000’ this will make the ‘Compare Activities’ test to wait 4000 milliseconds before starting the test. This will give the workflow time to perform it’s other activities before completing. If this test fails then increase the time. It will then pass.
  34. The test script should now look like thistest-builder4
  35. Now click on ‘Save’
  36. You will now see a read only version of the test
  37. With two buttons ‘Edit Test’ and ‘Run Test’

Running a Test

To run a test, either select a test from the test list and click on ‘Run Test’ or from the test details click on the button ‘Run Test’

Once the test has been completed, you will be displayed the results for each individual test and also the overall test.

resuts

Each individual test will show whether it has passed or failed. If it has passed the status will be true and in green and if had failed it will be false and in red.

For each test that we create we also get the overall test result.

A failed test would be when any of the individual tests has failed

fail

It will show a pie chart showing the % of pass and fail with big black x cross above it.

pass

For an overall Pass we get a tick and of course a 100%

These test results will remain until, you click on ‘Run Test’ again and this will overwrite the previous test results with the new ones.

How is it built

So now that we seen how it works, lets look at how we put it together. It’s uses the main code base from my previous article on testing, but with some additional classes to handle the reflection and  some wrappers to make some of the methods accessible.

The new code can be downloaded from here, it is work progress and in it’s early stages.

Lets have a look at the new code

Wrapper class

There is a wrapper class called Endpoint.cs and it’s basically wrapping around the methods in WorkflowFramework.cs and making them public static methods so the K2 endpoint assembly broker can pick them up.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace K2WorkflowMap
{
 



 public class Endpoint
 {

/// <summary>
 /// Starts a workflow
 /// </summary>
 /// <param name="Folio"></param>
 /// <param name="WorkflowName"></param>
 /// <returns></returns>

public static int Start(string Folio, string WorkflowName)
 {
 int result = 0;
 WorkflowInstanceFramework framework = new WorkflowInstanceFramework();
 try
 {
 result = framework.StartProcess(Folio, WorkflowName);
 }
 catch (Exception ex)
 {

result = 0;
 }
 finally {

}

return result;
 }

/// <summary>
 /// Checks the current status of a workflow and returns it's status
 /// </summary>
 /// <param name="ProcessInstanceId"></param>
 /// <returns></returns>

public static string CheckWorkflowStatus(string ProcessInstanceId)
 {
 string result = string.Empty;
 WorkflowInstanceFramework framework = new WorkflowInstanceFramework();

try
 {
 result = framework.GetWorkflowStatus(ProcessInstanceId);
 }
 catch (Exception ex)
 {

result = ex.Message;
 }
 finally
 {

}

return result;

}

/// <summary>
 /// Checks to see if a task exists and returns true
 /// </summary>
 /// <param name="ProcessInstanceId"></param>
 /// <param name="ProcessName"></param>
 /// <returns></returns>
 public static bool TasksExist(string ProcessInstanceId, string ProcessName)
 {
 bool result = false;
 WorkflowInstanceFramework framework = new WorkflowInstanceFramework();
 try
 {
 result = framework.IsTaskFound(ProcessInstanceId, ProcessName);
 }
 catch (Exception ex)
 {

result = false;
 }
 finally
 {

}

return result;
 }

/// <summary>
 /// Checks the number of tasks for a workflow instance and returns the number of tasks available
 /// </summary>
 /// <param name="ProcessInstanceId"></param>
 /// <param name="ProcessName"></param>
 /// <returns></returns>
 public static int NumberOfTasks(string ProcessInstanceId, string ProcessName)
 {
 int result = 0;
 WorkflowInstanceFramework framework = new WorkflowInstanceFramework();
 try
 {
 result = framework.GetTaskCount(ProcessInstanceId, ProcessName);
 }
 catch (Exception ex)
 {

result = 0;
 }
 finally
 {

}

return result;
 }


 /// <summary>
 /// Gets tasks details
 /// </summary>
 /// <param name="ProcessInstanceId"></param>
 /// <param name="ProcessName"></param>
 /// <returns></returns>
 public static tasklist GetTask(string ProcessInstanceId, string ProcessName)
 {

tasklist result = new tasklist();
 WorkflowInstanceFramework framework = new WorkflowInstanceFramework();
 try
 {
 result = framework.GetTask(ProcessInstanceId, ProcessName)[0];

}
 catch (Exception ex)
 {

result.Status = ex.Message;
 }
 finally
 {

}

return result;
 }
 
 /// <summary>
 /// Opens a task
 /// </summary>
 /// <param name="SN"></param>
 /// <param name="DestinationUser"></param>
 /// <returns></returns>
 public static List<TaskDetails> OpenTaskDetails(string SN, string DestinationUser)
 {

List<TaskDetails> result = new List<TaskDetails>();
 WorkflowInstanceFramework framework = new WorkflowInstanceFramework();
 try
 {
 result = framework.OpenTask(SN, DestinationUser);
 }
 catch (Exception ex)
 {

result.Add(new TaskDetails
 {
 Status = ex.Message

});
 }
 finally
 {

}

return result;
 }

/// <summary>
/// Gets a list of actions for a task
/// </summary>
/// <param name="SN"></param>
/// <param name="DestinationUser"></param>
/// <returns></returns>
 public static List<ActionList> TaskActions(string SN, string DestinationUser)
 {

List<ActionList> result = new List<ActionList>();

TaskDetails task = new TaskDetails();
 WorkflowInstanceFramework framework = new WorkflowInstanceFramework();
 try
 {
 task = framework.OpenTask(SN, DestinationUser)[0];

result = framework.WhatCanIdo(task.Actions);


 }
 catch (Exception ex)
 {

result.Add(new ActionList
 {
 ActionText = ex.Message

});
 }
 finally
 {

}

return result;

}

/// <summary>
 /// Actions a task
 /// </summary>
 /// <param name="SN"></param>
 /// <param name="Action"></param>
 /// <param name="DestinationUser"></param>
 /// <returns></returns>
 public static bool CompleteTask(string SN, string Action, string DestinationUser)
 {

bool result = false;
 WorkflowInstanceFramework framework = new WorkflowInstanceFramework();
 try
 {
 
 result = framework.ActionTask(Action,SN,DestinationUser);


 }
 catch (Exception ex)
 {

result = false;
 }
 finally
 {

}

return result;

}

/// <summary>
 /// Gets a list of activities for a process instance
 /// </summary>
 /// <param name="ProcessInstanceId"></param>
 /// <returns></returns>
 public static List<Activities> GetActivities(string ProcessInstanceId)
 {
 WorkflowInstanceFramework framework = new WorkflowInstanceFramework();
 List<Activities> result = new List<Activities>();


 try
 {

result = framework.GetActivities(ProcessInstanceId);


 }
 catch (Exception ex)
 {

result.Add(new Activities {
 ActivityName = ex.Message.ToString()

});
 }
 finally
 {

}

return result;

}

/// <summary>
 /// Gets events in activity
 /// </summary>
 /// <param name="ActivityId"></param>
 /// <returns></returns>
 public static List<Events> GetEvents(string ActivityId)
 {
 WorkflowInstanceFramework framework = new WorkflowInstanceFramework();
 List<Events> result = new List<Events>();


 try
 {

result = framework.GetEvents(ActivityId);


 }
 catch (Exception ex)
 {

result.Add(new Events
 {
 EventName = ex.Message.ToString()

});
 }
 finally
 {

}

return result;


 }


 /// <summary>
 /// Gets a list of datafields for process instance
 /// </summary>
 /// <param name="ProcessInstanceId"></param>
 /// <returns></returns>
 public static List<dataField> GetProcessDataFields(string ProcessInstanceId)
 {
 WorkflowInstanceFramework framework = new WorkflowInstanceFramework();
 List<dataField> result = new List<dataField>();


 try
 {

result = framework.GetProcessDataFields(ProcessInstanceId);


 }
 catch (Exception ex)
 {

result.Add(new dataField
 {
 Name = ex.Message.ToString()

});
 }
 finally
 {

}

return result;
 }


 /// <summary>
 /// Gets a list of processes
 /// </summary>
 /// <returns></returns>
 public static List<Workflow> GetProcesses()
 {

WorkflowInstanceFramework framework = new WorkflowInstanceFramework();
 List<Workflow> result = new List<Workflow>();


 try
 {

result = framework.GetProcesses();


 }
 catch (Exception ex)
 {

result.Add(new Workflow
 {
 Name = ex.Message.ToString()

});
 }
 finally
 {

}

return result;

}

/// <summary>
 /// Gets lists of activities at design timne
 /// </summary>
 /// <param name="ProcId"></param>
 /// <returns></returns>
 public static List<ActivityDesign> ActivityDesign(int ProcId)
 {
 WorkflowInstanceFramework framework = new WorkflowInstanceFramework();
 List<ActivityDesign> result = new List<ActivityDesign>();


try
 {

result = framework.GetWorkflowActivities(ProcId);


 }
 catch (Exception ex)
 {

result.Add(new ActivityDesign
 {
 Name = ex.Message.ToString()

});
 }
 finally
 {

}

return result;
 }

}

}

 

Power of reflection

The next class  is called Testplan.cs and this the class that does all the work from carrying out the tests to using reflection to produce a list of tests based on the methods in Workflowframework.cs. In this class there is a couple of key methods that make this possible.

Method: GetMethods

This method reads through the Workflowframework.cs and makes a list of all the methods and it’s parameters. It’s this method that we use in the Smartform to select the lists types

test-builder3

public static List<MethodList> GetMethods()
 {
 List<MethodList> list = new List<MethodList>();
 WorkflowInstanceFramework wf = new WorkflowInstanceFramework();
 foreach (var meth in wf.GetType().GetMethods())
 {
 string Left = string.Empty;
 string Middle = string.Empty;
 string Right = string.Empty;

switch (meth.GetParameters().Count())
 {
 case 1:
 {
 Left = meth.GetParameters()[0].Name;
 break;
 }
 case 2:
 {
 Left = meth.GetParameters()[0].Name;
 Middle = meth.GetParameters()[1].Name;
 break;
 }
 case 3:
 {
 Left = meth.GetParameters()[0].Name;
 Middle = meth.GetParameters()[1].Name;
 Right = meth.GetParameters()[2].Name;
 break;
 }

}
 


list.Add(new MethodList
 {
 MethodName = meth.Name,
 NumberOfParameters = meth.GetParameters().Count(),
 LeftParameter = Left,
 RightParameter = Right,
 MiddleParameter = Middle


 });

}

return list;

}

Method: Invoke

This basically allows methods to be called dynamically, by putting in the method name as a string and passing in the methods parameters. This method will then call the input method dynamically. It’s used to call the test methods from Workflowflowframework.cs

private static object Invoke(string typeName, string methodName, List<string> parameter, int Milliseconds, int parameters)
 {
 if (Milliseconds > 0)
 {
 Thread.Sleep(Milliseconds);
 }

object value = null;

try
 {
 Type type = Type.GetType(typeName);
 object instance = Activator.CreateInstance(type);
 System.Reflection.MethodInfo method = type.GetMethod(methodName);


 System.Reflection.ConstructorInfo constructor = type.GetConstructor(Type.EmptyTypes);
 object classobj = constructor.Invoke(new object[] { });

switch (parameters)
 {
 case 1:
 {
 value = method.Invoke(classobj, new object[] { parameter[0] });
 break;
 }
 case 2:
 {
 value = method.Invoke(classobj, new object[] { parameter[0], parameter[1] });
 break;
 }
 case 3:
 {
 value = method.Invoke(classobj, new object[] { parameter[0], parameter[1], parameter[2] });
 break;
 }

}

 }
 catch (Exception ex)
 {

value = ex.Message;
 }
 finally
 {
 
 }
 return value;
 }

 

Method: RunTest

This method actually runs the tests, we pass in the ‘TestId’ which we use to get a list of tests that we want to carry out on a workflow. We then loop through each test calling the invoke method  where we pass in the method name and parameters. Some parameters can not be got at the design time of building the test.

So method looks out for key words such as ‘ProcessInstanceId’ and will then replace it with the actual value

It then takes the return value and then compare it against the expected value, which will return true if it passes or false if it fails. This result is then recorded back into the database against the test.

Once it has finished all the tests. It then works if the overall test for a Pass or a Fail, by looking at the results for each test and seeing if there are any fails.

public static void RunTest(int TestId)
 {
 object Result = null;
 List<string> parameters = new List<string>();
 try
 {

List<TestPlanTest> tests = BuildTestPlan(TestId);
 if (tests.Count >= 1)
 {

foreach (var test in tests)
 {
 var Actual = string.Empty;
 switch (test.NumberOfParameters)
 {
 case 1:
 {

parameters.Add(AutoBuildTestParameters(test.LeftParameter, TestId));
 break;
 }
 case 2:
 {
 parameters.Add(AutoBuildTestParameters(test.LeftParameter, TestId));
 parameters.Add(AutoBuildTestParameters(test.MiddleParameter, TestId));

break;
 }
 case 3:
 {
 parameters.Add(AutoBuildTestParameters(test.LeftParameter, TestId));
 parameters.Add(AutoBuildTestParameters(test.MiddleParameter, TestId));
 parameters.Add(AutoBuildTestParameters(test.RightParameter, TestId));

break;
 }

}


 Result = Invoke("K2WorkflowMap.WorkflowInstanceFramework", test.TestType.ToString(), parameters, test.Milliseconds, test.NumberOfParameters);
 if (test.TestType == "StartProcess")
 {
 UpdateTestDetails(TestId, (int)Result, parameters[0].ToString(), "bob", DateTime.Today, true);
 }
 RecordResult(test.TestRunId, Result.ToString(), test.ExpectedResult,test.Sign);

parameters.Clear();

}

}

}
 catch (Exception ex)
 { }

finally
 {
 UpdateTestDetailsEnd(TestId);

}


 }

 

Full code for Testplan.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Text;
using System.Threading.Tasks;

namespace K2WorkflowMap
{
 public class TestPlan
 {
 /// <summary>
 /// SmartObject call to get a certain test plan
 /// </summary>
 /// <param name="TestId"></param>
 /// <returns></returns>
 public static List<TestPlanTest> BuildTestPlan(int TestId)
 {

List<TestPlanTest> list = new List<TestPlanTest>();


 SourceCode.Hosting.Client.BaseAPI.SCConnectionStringBuilder hostServerConnectionString = new SourceCode.Hosting.Client.BaseAPI.SCConnectionStringBuilder();
 hostServerConnectionString.Host = "localhost";
 hostServerConnectionString.Port = 5555;
 hostServerConnectionString.IsPrimaryLogin = true;
 hostServerConnectionString.Integrated = true;

SourceCode.SmartObjects.Client.SmartObjectClientServer serverName = new SourceCode.SmartObjects.Client.SmartObjectClientServer();
 serverName.CreateConnection();
 serverName.Connection.Open(hostServerConnectionString.ToString());

try
 {

SourceCode.SmartObjects.Client.SmartObject smartObject = serverName.GetSmartObject("K2C_TST_SMO_TestRun");

smartObject.MethodToExecute = "List_1";
 smartObject.GetMethod("List_1").Parameters["pTestId"].Value = TestId.ToString();
 SourceCode.SmartObjects.Client.SmartObjectList smoList = serverName.ExecuteList(smartObject);
 foreach (SourceCode.SmartObjects.Client.SmartObject item in smoList.SmartObjectsList)
 {


 var z = item.Properties["Sign"].Value;


 int testrunid = 0;
 int parameters = 0;
 int Milliseconds = 0;
 int.TryParse(item.Properties["NumberOfParameters"].Value, out parameters);
 int.TryParse(item.Properties["TestRunId"].Value, out testrunid);
 int.TryParse(item.Properties["Milliseconds"].Value, out Milliseconds);

list.Add(new TestPlanTest
 {
 TestRunId = testrunid,

TestType = item.Properties["TestType"].Value,
 ExpectedResult = item.Properties["ExpectedResult"].Value,
 LeftParameter = item.Properties["LeftParameter"].Value,
 MiddleParameter = item.Properties["MiddleParameter"].Value,
 RightParameter = item.Properties["RightParameter"].Value,
 Milliseconds = Milliseconds,
 NumberOfParameters = parameters,
 Sign = item.Properties["Sign"].Value

});
 }

}


 catch (Exception ex)
 {
 list.Add(new TestPlanTest
 {


 });

}
 finally
 {

serverName.Connection.Close();
 }

return list;

}

/// <summary>
 /// Gets the test details
 /// </summary>
 /// <param name="TestId"></param>
 /// <returns></returns>
 public static List<TestPlanDetails> GetTestDetails(int TestId)
 {

List<TestPlanDetails> list = new List<TestPlanDetails>();


 SourceCode.Hosting.Client.BaseAPI.SCConnectionStringBuilder hostServerConnectionString = new SourceCode.Hosting.Client.BaseAPI.SCConnectionStringBuilder();
 hostServerConnectionString.Host = "localhost";
 hostServerConnectionString.Port = 5555;
 hostServerConnectionString.IsPrimaryLogin = true;
 hostServerConnectionString.Integrated = true;

SourceCode.SmartObjects.Client.SmartObjectClientServer serverName = new SourceCode.SmartObjects.Client.SmartObjectClientServer();
 serverName.CreateConnection();
 serverName.Connection.Open(hostServerConnectionString.ToString());

try
 {

SourceCode.SmartObjects.Client.SmartObject smartObject = serverName.GetSmartObject("K2C_TST_SMO_TestRun");
 SourceCode.SmartObjects.Client.SmartObject returnSmartObject;
 smartObject.MethodToExecute = "List_NaN_NaN";
 smartObject.GetMethod("List_NaN_NaN").Parameters["pTestId"].Value = TestId.ToString();
 
 returnSmartObject = serverName.ExecuteScalar(smartObject);


int ProcessTypeId = 0;
 int ProcessInstanceId = 0;
 int parameters = 0;
 DateTime StartedDate = DateTime.Today;
 DateTime FinishedDate = DateTime.Today;
 int.TryParse(returnSmartObject.Properties["ProcessTypeId"].Value, out ProcessTypeId);
 int.TryParse(returnSmartObject.Properties["ProcessInstanceId"].Value, out ProcessInstanceId);
 DateTime.TryParse(returnSmartObject.Properties["StartedDate"].Value, out StartedDate);
 DateTime.TryParse(returnSmartObject.Properties["FinishedDate"].Value, out FinishedDate);
 list.Add(new TestPlanDetails
 {
 TestName = returnSmartObject.Properties["TestName"].Value,
 WorkflowName = returnSmartObject.Properties["WorkflowName"].Value,
 ProcessTypeId = ProcessTypeId,
 ProcessInstanceId = ProcessInstanceId,
 Folio = returnSmartObject.Properties["Folio"].Value,
 Originator = returnSmartObject.Properties["Originator"].Value,
 Started = returnSmartObject.Properties["Started"].Value,
 StartedDate = StartedDate,
 Finished = returnSmartObject.Properties["Finished"].Value,
 FinishedDate = FinishedDate,
 Status = returnSmartObject.Properties["Status"].Value,
 Route = returnSmartObject.Properties["Route"].Value

});


}


 catch (Exception ex)
 {
 list.Add(new TestPlanDetails
 {
 TestName = ex.Message

});

}
 finally
 {

serverName.Connection.Close();
 }

return list;

}

/// <summary>
 /// Updates the test details to say it has started
 /// </summary>
 /// <param name="TestDetailsid"></param>
 /// <param name="ProcessInstanceId"></param>
 /// <param name="Folio"></param>
 /// <param name="Originator"></param>
 /// <param name="StartedDate"></param>
 /// <param name="Started"></param>
 private static void UpdateTestDetails(int TestDetailsid, int ProcessInstanceId, string Folio, string Originator, DateTime StartedDate, bool Started)
 {
 SourceCode.Hosting.Client.BaseAPI.SCConnectionStringBuilder hostServerConnectionString = new SourceCode.Hosting.Client.BaseAPI.SCConnectionStringBuilder();
 hostServerConnectionString.Host = "localhost";
 hostServerConnectionString.Port = 5555;
 hostServerConnectionString.IsPrimaryLogin = true;
 hostServerConnectionString.Integrated = true;

SourceCode.SmartObjects.Client.SmartObjectClientServer serverName = new SourceCode.SmartObjects.Client.SmartObjectClientServer();
 serverName.CreateConnection();
 serverName.Connection.Open(hostServerConnectionString.ToString());

try
 {

SourceCode.SmartObjects.Client.SmartObject smartObject = serverName.GetSmartObject("K2C_TST_SMO_TestRun");
 smartObject.MethodToExecute = "Execute_NaN_1";
 smartObject.GetMethod("Execute_NaN_1").Parameters["pTestDetailsid"].Value = TestDetailsid.ToString();
 smartObject.GetMethod("Execute_NaN_1").Parameters["pProcessInstanceId"].Value = ProcessInstanceId.ToString();
 smartObject.GetMethod("Execute_NaN_1").Parameters["pFolio"].Value = Folio.ToString();
 smartObject.GetMethod("Execute_NaN_1").Parameters["pOriginator"].Value = Originator.ToString();
 smartObject.GetMethod("Execute_NaN_1").Parameters["pStartedDate"].Value = StartedDate.ToString();
 smartObject.GetMethod("Execute_NaN_1").Parameters["pStarted"].Value = Started.ToString();
 serverName.ExecuteScalar(smartObject);
 }
 catch(Exception ex)
 {
 string x = ex.Message;
 }
 finally
 {
 serverName.Connection.Close();
 }
 }

/// <summary>
 /// Update the test details to say it has finished
 /// </summary>
 /// <param name="TestId"></param>
 private static void UpdateTestDetailsEnd(int TestId)
 {


SourceCode.Hosting.Client.BaseAPI.SCConnectionStringBuilder hostServerConnectionString = new SourceCode.Hosting.Client.BaseAPI.SCConnectionStringBuilder();
 hostServerConnectionString.Host = "localhost";
 hostServerConnectionString.Port = 5555;
 hostServerConnectionString.IsPrimaryLogin = true;
 hostServerConnectionString.Integrated = true;

SourceCode.SmartObjects.Client.SmartObjectClientServer serverName = new SourceCode.SmartObjects.Client.SmartObjectClientServer();
 serverName.CreateConnection();
 serverName.Connection.Open(hostServerConnectionString.ToString());

try
 {

SourceCode.SmartObjects.Client.SmartObject smartObject = serverName.GetSmartObject("K2C_TST_SMO_TestRun");
 smartObject.MethodToExecute = "Execute_NaN_NaN_1";
 smartObject.GetMethod("Execute_NaN_NaN_1").Parameters["pTestId"].Value = TestId.ToString();
 serverName.ExecuteScalar(smartObject);
 }
 catch (Exception ex)
 { string x = ex.Message; }
 finally
 {
 serverName.Connection.Close();
 }
 }

/// <summary>
/// Replaces parameters with their runtime value
/// </summary>
/// <param name="parametername"></param>
/// <param name="TestId"></param>
/// <returns></returns>
 public static string AutoBuildTestParameters(string parametername, int TestId)
 {


 string ProcessInstanceId = "0";
 string ProcessName = string.Empty;

string paramName = parametername.ToLower();


 string result = string.Empty;
 switch (paramName)
 {
 case "serialnumber":
 {
 K2WorkflowMap.WorkflowInstanceFramework workflow = new K2WorkflowMap.WorkflowInstanceFramework();
 ProcessInstanceId = GetTestDetails(TestId)[0].ProcessInstanceId.ToString();
 ProcessName = GetTestDetails(TestId)[0].WorkflowName;
 var task = workflow.GetTask(ProcessInstanceId, ProcessName);
 result = task[0].SerialNumber;

break;
 }
 case "sn":
 {
 K2WorkflowMap.WorkflowInstanceFramework workflow = new K2WorkflowMap.WorkflowInstanceFramework();
 ProcessInstanceId = GetTestDetails(TestId)[0].ProcessInstanceId.ToString();
 ProcessName = GetTestDetails(TestId)[0].WorkflowName;
 var task = workflow.GetTask(ProcessInstanceId, ProcessName);
 result = task[0].SerialNumber;

break;
 }
 case "processinstanceid":
 {
 
 result = GetTestDetails(TestId)[0].ProcessInstanceId.ToString();
 break;
 }
 case "activities":
 {
 
 result = GetTestDetails(TestId)[0].Route.ToString();
 break;
 }

case "destinationuser":
 {

break;
 }
 default:
 {
 result = parametername;
 break;
 }

}

return result;
 }

/// <summary>
 /// Runs the test
 /// </summary>
 /// <param name="TestId"></param>
 public static void RunTest(int TestId)
 {
 object Result = null;
 List<string> parameters = new List<string>();
 try
 {

List<TestPlanTest> tests = BuildTestPlan(TestId);
 if (tests.Count >= 1)
 {

foreach (var test in tests)
 {
 var Actual = string.Empty;
 switch (test.NumberOfParameters)
 {
 case 1:
 {

parameters.Add(AutoBuildTestParameters(test.LeftParameter, TestId));
 break;
 }
 case 2:
 {
 parameters.Add(AutoBuildTestParameters(test.LeftParameter, TestId));
 parameters.Add(AutoBuildTestParameters(test.MiddleParameter, TestId));

break;
 }
 case 3:
 {
 parameters.Add(AutoBuildTestParameters(test.LeftParameter, TestId));
 parameters.Add(AutoBuildTestParameters(test.MiddleParameter, TestId));
 parameters.Add(AutoBuildTestParameters(test.RightParameter, TestId));

break;
 }

}


 Result = Invoke("K2WorkflowMap.WorkflowInstanceFramework", test.TestType.ToString(), parameters, test.Milliseconds, test.NumberOfParameters);
 if (test.TestType == "StartProcess")
 {
 UpdateTestDetails(TestId, (int)Result, parameters[0].ToString(), "bob", DateTime.Today, true);
 }
 RecordResult(test.TestRunId, Result.ToString(), test.ExpectedResult,test.Sign);

parameters.Clear();

}

}

}
 catch (Exception ex)
 { }

finally
 {
 UpdateTestDetailsEnd(TestId);

}


 }

/// <summary>
 /// Reflection method, to dynamically call a method in class 
 /// </summary>
 /// <param name="typeName"></param>
 /// <param name="methodName"></param>
 /// <param name="parameter"></param>
 /// <param name="Milliseconds"></param>
 /// <param name="parameters"></param>
 /// <returns></returns>
 private static object Invoke(string typeName, string methodName, List<string> parameter, int Milliseconds, int parameters)
 {
 if (Milliseconds > 0)
 {
 Thread.Sleep(Milliseconds);
 }

object value = null;

try
 {
 Type type = Type.GetType(typeName);
 object instance = Activator.CreateInstance(type);
 System.Reflection.MethodInfo method = type.GetMethod(methodName);


 System.Reflection.ConstructorInfo constructor = type.GetConstructor(Type.EmptyTypes);
 object classobj = constructor.Invoke(new object[] { });

switch (parameters)
 {
 case 1:
 {
 value = method.Invoke(classobj, new object[] { parameter[0] });
 break;
 }
 case 2:
 {
 value = method.Invoke(classobj, new object[] { parameter[0], parameter[1] });
 break;
 }
 case 3:
 {
 value = method.Invoke(classobj, new object[] { parameter[0], parameter[1], parameter[2] });
 break;
 }

}
 // method.Invoke(instance, null);

// value = method.Invoke(classobj, new object[] { parameter.ToArray()});
 }
 catch (Exception ex)
 {

value = ex.Message;
 }
 finally
 {
 
 }
 return value;
 }

/// <summary>
 /// Records the test result
 /// </summary>
 /// <param name="TestRunId"></param>
 /// <param name="Actual"></param>
 /// <param name="Expected"></param>
 /// <param name="Sign"></param>
 private static void RecordResult(int TestRunId, object Actual, string Expected,string Sign)
 {
 SourceCode.Hosting.Client.BaseAPI.SCConnectionStringBuilder hostServerConnectionString = new SourceCode.Hosting.Client.BaseAPI.SCConnectionStringBuilder();
 hostServerConnectionString.Host = "localhost";
 hostServerConnectionString.Port = 5555;
 hostServerConnectionString.IsPrimaryLogin = true;
 hostServerConnectionString.Integrated = true;

SourceCode.SmartObjects.Client.SmartObjectClientServer serverName = new SourceCode.SmartObjects.Client.SmartObjectClientServer();
 serverName.CreateConnection();
 serverName.Connection.Open(hostServerConnectionString.ToString());

try
 {

SourceCode.SmartObjects.Client.SmartObject smartObject = serverName.GetSmartObject("K2C_TST_SMO_TestRun");
 smartObject.MethodToExecute = "Execute_NaN";
 smartObject.GetMethod("Execute_NaN").Parameters["pTestRunId"].Value = TestRunId.ToString();
 smartObject.GetMethod("Execute_NaN").Parameters["pResult"].Value = Actual.ToString();
 smartObject.GetMethod("Execute_NaN").Parameters["pExpectedResult"].Value = Expected;
 smartObject.GetMethod("Execute_NaN").Parameters["pPass"].Value = Result(Actual, Expected, Sign).ToString();


 serverName.ExecuteScalar(smartObject);

}
 catch (Exception ex)
 {
 string x = ex.Message;
 }
 finally
 {
 serverName.Connection.Close();
 }


 }

/// <summary>
 /// Works out the result
 /// </summary>
 /// <param name="Actual"></param>
 /// <param name="Expected"></param>
 /// <param name="Sign"></param>
 /// <returns></returns>
 private static Boolean Result(object Actual, string Expected, string Sign)
 {
 Boolean Result = false;
 switch (Sign)
 {
 case "Equals":
 {
 Result = ((string)Actual == Expected);
 break;
 }
 case "Less Than":
 {
 int a = int.Parse((string)Actual);
 int e = int.Parse(Expected);
 Result = (a < e);
 break;
 }
 case "Less Than Equal":
 {
 int a = int.Parse((string)Actual);
 int e = int.Parse(Expected);
 Result = (a <= e);
 break;
 }

case "Greater Than":
 {
 int a = int.Parse((string)Actual);
 int e = int.Parse(Expected);
 Result = (a > e);
 break;
 }
 case "Greater Than Equal":
 {
 int a = int.Parse((string)Actual);
 int e = int.Parse(Expected);
 Result = (a >= e);
 break;
 }
 }

return Result;
 }

/// <summary>
 /// Reflection method to build a list based on methods in a class
 /// </summary>
 /// <returns></returns>
 public static List<MethodList> GetMethods()
 {
 List<MethodList> list = new List<MethodList>();
 WorkflowInstanceFramework wf = new WorkflowInstanceFramework();
 foreach (var meth in wf.GetType().GetMethods())
 {
 string Left = string.Empty;
 string Middle = string.Empty;
 string Right = string.Empty;

switch (meth.GetParameters().Count())
 {
 case 1:
 {
 Left = meth.GetParameters()[0].Name;
 break;
 }
 case 2:
 {
 Left = meth.GetParameters()[0].Name;
 Middle = meth.GetParameters()[1].Name;
 break;
 }
 case 3:
 {
 Left = meth.GetParameters()[0].Name;
 Middle = meth.GetParameters()[1].Name;
 Right = meth.GetParameters()[2].Name;
 break;
 }

}
 


list.Add(new MethodList
 {
 MethodName = meth.Name,
 NumberOfParameters = meth.GetParameters().Count(),
 LeftParameter = Left,
 RightParameter = Right,
 MiddleParameter = Middle


 });

}

return list;

}


 }
}

 

Smartforms

The Smartforms used are pretty much straight forward and are included in the solution. It does use the excellent High Charts custom control for graphs which was created by the master custom control builder Adam Castle. (Check out is other controls they are  awesome) You will need a license for High Charts, but its really cheap.

 

What Next

This is  a work in progress so there will be some bugs, but I wanted see if testing a workflow could also be done in a no code way and it can. Please feel free to  change the code etc.. and share it back so that everyone can get the benefit and I will continue to modify the solution.

So what else can be done to make it better

  1. Handle IPC events
  2. Make it work with workflows where they can loop back
  3. PDF Report

 

 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s