February 16, 2021

Using Test Files in a Unit Test with NUnit

Insert the files into the project under a new directory say “TestData”. Include them in the project and change the build action on each file such that the “Copy to Output Directory” option should be “Copy if newer”.

Somewhere in your test:
// Create a path to the TestData directory using the 
// NUnit “TestContext.CurrentContext.TestDirectory” property
private string TestFilePath = Path.Combine(TestContext.CurrentContext.TestDirectory, "TestData");
// Now you have access to the files
string testFilePath = Path.Combine(TestFilePath, @"TestFile1.mib");

Example of using Moq's MockSequence in a Unit Test

This test shows how to test Properties and Methods
[Test]
[Category("AutomaticTest")]
public void Some_Test()
{
    var mockPublisher = new Mock(MockBehavior.Strict);
    var sequence = new MockSequence();
    mockPublisher.InSequence(sequence).Setup(x => x.Start());
    mockPublisher.InSequence(sequence).SetupSet(x => x.IsSynchronizing = true);
    mockPublisher.InSequence(sequence).Setup(x => x.Start());
    mockPublisher.InSequence(sequence).Setup(x => x.Complete());
    mockPublisher.InSequence(sequence).Setup(x => x.Start());
    mockPublisher.InSequence(sequence).Setup(x => x.Complete());
    mockPublisher.InSequence(sequence).Setup(x => x.Complete());
    mockPublisher.InSequence(sequence).SetupSet(x => x.IsSynchronizing = false);

    var fakeSyncObserver = new FakeSystemReportersSyncObserver();
    var fakeConnectivityObserver = new FakeEventEngineConnectivityObserver();
    Monitor Monitor = new Monitor(mockPublisher.Object, fakeSyncObserver, fakeConnectivityObserver);

    fakeSyncObserver.PublishStateChanged(new SyncEventArgs("1", 
      SyncEnum.ReportSynchronized));    // Start(), IsSynchronizing = true
    fakeSyncObserver.PublishStateChanged(new SyncEventArgs("2", 
      SyncEnum.ReportSynchronized));    // Start() (IsSynchronizing is already true)
    fakeSyncObserver.PublishStateChanged(new SyncEventArgs("2", 
      SyncEnum.Completed)); // Complete() ("1" is still syncing)
    fakeSyncObserver.PublishStateChanged(new SyncEventArgs("2", 
      SyncEnum.ReportSynchronized));    // Start() (IsSynchronizing is already true)
    fakeSyncObserver.PublishStateChanged(new SyncEventArgs("2", 
      SyncEnum.Completed)); // Complete() ("1" is still syncing)
    fakeSyncObserver.PublishStateChanged(new SyncEventArgs("1", 
      SyncEnum.Completed)); // Complete(), IsSynchronizing = false ("1" and "2" are both complete now)

    mockPublisher.VerifyAll();
    mockPublisher.Verify(m => m.Fail(), Times.Never())
    mockPublisher.Verify(m => m.Start(), Times.Exactly(3));
}
MockSequence has a bug, make sure that there is a call of "VerifyAll()" followed by at least one call to "Verify()" otherwise the MockSequence may not actually be checked!
Here is another sample test:
[Test]
[Category("AutomaticTest")]
public void AnotherSampleTest()
{
    var mockSnmpPublisher = new Mock(MockBehavior.Strict);
    // Create the MockSequence to validate the call order
    var sequence = new MockSequence();
    // Create the expectations, notice that the Setup is called via InSequence
    mockSnmpPublisher.InSequence(sequence).Setup(
        x => x.SendTrap(
            It.Is(y => y.Equals(StatusOids.Traps.SynchronizationStarted)),
            It.Is(s => s == true)));
    mockSnmpPublisher.InSequence(sequence).
      Setup(x => x.SetVariable(It.Is(y => y.Equals(StatusOids.Variables.IsSynchronizing)), 
                      It.Is(y => y.Equals(IsSynchronizing))));
    mockSnmpPublisher.InSequence(sequence).Setup(
        x => x.SendTrap(
            It.Is(y => y.Equals(StatusOids.Traps.SynchronizationCompleted)),
            It.Is(s => s == true)));
    mockSnmpPublisher.InSequence(sequence).
           Setup(x => x.SetVariable(It.Is(y => y.Equals(StatusOids.Variables.IsSynchronizing)), 
                    It.Is(y => y.Equals(IsNotSynchronizing))));

    SyncSnmpPublisher SyncSnmpPublisher = new SyncSnmpPublisher(mockSnmpPublisher.Object);
    SyncSnmpPublisher.Start();
    SyncSnmpPublisher.IsSynchronizing = true;
    SyncSnmpPublisher.Complete();
    SyncSnmpPublisher.IsSynchronizing = false;

    mockSnmpPublisher.VerifyAll();
    mockSnmpPublisher.Verify(m =>  // REMEMBER this will ensure the MockSequence will be checked
        m.SendTrap(It.Is(y => y.Equals(StatusOids.Traps.SynchronizationCompleted)), 
            It.Is(s => s == true)), Times.Once());
}

February 15, 2021

Hard Symbolic Links For Developers

Had a problem where the output of a (DEBUG) build went to one directory but the to run it needed to go somewhere else. This was easy to circumvent using hard links. Here is the command line for making a hard link (requires Administrator permissions)
mklink /J {SourceDirectory} {TargetDirectory}

The Source directory must NOT exist and the Target directory must. This is because the Source directory is created as a symbolic link to the hard directory
Here was my script:
mklink /J "C:\Src\repos\XxxRepo\bin\Debug\TheProduct.Plugins\ICMP" 
          "C:\Src\repos\XxxRepo\bin\Debug\TheProduct.Service\Plugins\ICMP"
mklink /J "C:\Src\repos\XxxRepo\bin\Debug\TheProduct.Plugins\RelayBoard" 
          "C:\Src\repos\XxxRepo\bin\Debug\TheProduct.Service\Plugins\RelayBoard"
mklink /J "C:\Src\repos\XxxRepo\bin\Debug\TheProduct.Plugins\SeveritySetter" 
          "C:\Src\repos\XxxRepo\bin\Debug\TheProduct.Service\Plugins\SeveritySetter"
mklink /J "C:\Src\repos\XxxRepo\bin\Debug\TheProduct.Plugins\SMTP" 
          "C:\Src\repos\XxxRepo\bin\Debug\TheProduct.Service\Plugins\SMTP"
mklink /J "C:\Src\repos\XxxRepo\bin\Debug\TheProduct.Plugins\WMI" 
          "C:\Src\repos\XxxRepo\bin\Debug\TheProduct.Service\Plugins\WMI"

November 18, 2020

Conditional Null Operator ?.

expression1?.expression2  => expression1 != null ? expression2 : null
This expression evaluates to: expression2, if expression1 is Not-Null. else, it evaluates to null
What happens when a conditional operator is inside a boolean expression?
void Main()
{
    A a = new A();
    if (a?.BVal?.Value == 4)
	{
	    Console.WriteLine("boolean expression returns true");
	}
	else
	{
		Console.WriteLine("boolean expression returns false"); // This line is output
	}
}

public class B
{
   public int Value { get; set; }
}

public class A
{
   public B BVal { get; set; }
}
A related operator is ??=
variable ??= expression // if (variable is null) { variable = expression; }

September 29, 2020

Extension methods to Convert Strings to Various Fundamental types

These extension methods converts strings to various types (int, bool, float)
When the string is invalid they return a fallback value.
public static class StringParserExtensions
{
	public static int ParseInt(this string part, int fallbackValue = 0)
	{
		if (!int.TryParse(part.Trim(), out int anInt))
		{
			anInt = fallbackValue;
		}
		return anInt;
	}

	public static bool ParseBool(this string part, bool fallbackValue = false)
	{
		if (!bool.TryParse(part.Trim(), out bool aBool))
		{
			aBool = fallbackValue;
		}
		return aBool;
	}

	public static float ParseFloat(this string part, float fallbackValue = 0.0f)
	{
		if (!float.TryParse(part.Trim(), out float number))
		{
			number = fallbackValue;
		}
		return number;
	}
}
Example usage:
  stationLocation.StationId = parts[0].ParseInt(0); 

Linq GroupBy/ToLookUp

These are Linq methods to group elements in a sequence. ToLookup() is the same as GroupBy(); the only differences are that GroupBy execution is deferred, whereas ToLookup() execution is immediate and also ToLookup() is not available in linq query form only the method form.

Note that the resultant group object exposes itself in 2 ways:

1. A 'Key' property exposes a value which all the items in the group have in common (this could be a calculated value)

2. The group object itself is an IEnumerable of the items in the group.

This operation will iterate over all the items in the enumeration. Works well with the Linq method "ToDictionary()" to convert a sequence into a dictionary.

Here is an example:

// Read StationLocationRaw(s) (int StationId and string Location)
// from the Station Locations file
public class StationLocationReader 
{
    FileInfo fi;
    public StationLocationReader(string path)
    {
        fi = new FileInfo(path);
    }

    public IDictionary<int, string> StationLocationsByStationId()
    {
        var stationLocationsDictionary =
            ReadStationLocations(). // returns StationLocationRaw(s)
                                    // (int StationId and string Location)
            GroupBy(s => s.StationId).
            ToDictionary(g => g.Key, g => g.Single().Location ?? "");
        return stationLocationsDictionary;
    }

    public IEnumerable<StationLocationRaw> ReadStationLocations()
    {
        Debug.Assert(fi.Exists);
        var stationLocations = fi.
            ReadLines().
            Skip(1). // Skip the header line
            Select(line => ExtractStationLocation(line));
        return stationLocations;
    }

    private StationLocationRaw ExtractStationLocation(string line)
    {
        var stationLocation = new StationLocationRaw();
        var parts = line.Split('\t');
        if (parts.Length >= 2)
        {
            // ParseInt is an extension method to convert
            // to a string to an integer, returning 0 
            // when the string is invalid.
            stationLocation.StationId = parts[0].Trim().ParseInt(0); 
            stationLocation.Location = parts[1].Trim();
        }
        return stationLocation;
    }
}

February 9, 2020

Sample Regular Expression

[TestMethod]
public void ParseVideoFileForDateTime2Test()
{
    var filePath = @"D:\Temp\Nokia 6\Photos\OpenCamera\VID_20190623_085427.mp4";
    var fileNameSansExt = Path.GetFileNameWithoutExtension(filePath);

    var dateTimeFormat = "yyyyMMdd_HHmmss";
    var result = string.Empty;
    Regex regExp = null;
    CalcRegExpForDateTimeFormat(dateTimeFormat, out regExp);
    if (regExp != null)
    {
        result = regExp.MatchFirstRegExp(fileNameSansExt);
    }
    Assert.IsNotNull(regExp);
    Assert.IsTrue(result.Length == 15);
    Assert.IsTrue(result == "20190623_085427");
}

private static bool CalcRegExpForDateTimeFormat(
    string dateTimeFormat, out Regex regExp)
{
    var res = false;
    regExp = null;
    var newRegex = new StringBuilder(dateTimeFormat.Length);
    foreach (var ch in dateTimeFormat)
    {
        if ((ch == 'y') || (ch == 'M') || (ch == 'd') ||
            (ch == 'H') || (ch == 'm') || (ch == 's'))
        {
            newRegex.Append(@"\d");
        }
        else
        {
            newRegex.Append(ch);
        }
    }
    var regexStr = newRegex.ToString();
    res = (regexStr.Length > 0);
    if (res)
    {
        regExp = new Regex(regexStr, RegexOptions.Compiled);
    }
    return res;
}

public static class RegularExpressionExtensions
{
/// <summary>
/// Match the first occurance of a regular expression in a target string.
/// </summary>
/// <param name="target">string to search</param>
/// <param name="regexp">regular expression object to use for the
/// search</param>
/// <returns>first match of the regular expression otherwise an 
/// empty string if there is no match</returns>
  public static string MatchFirstRegExp(this Regex reg, string target)
  {
    var result = "";
    MatchCollection mc = reg.Matches(target);
    if (mc.Count > 0)
    {
        result = mc[0].Value;
    }
    return result;
  }
}