Advanced Scripting for Beijer iX Developer with TDD workflow

Setting up a test driven development environment in Visual Studio to develop Beijer iX software

Posted on 20.09.2021 - last edited on 21.09.2021

Developing iX software for a Beijer HMI panel was my first encounter with the .NET Compact Framework 3.5 (.NET CF) and my first time writing C# code outside of Visual Studio (VS). Instead I used the iX Developer IDE, provided by Beijer, to write and compile the project files and to load my changes onto the panel. Unsurprisingly it felt like quite a downgrade, coming from VS, for everything not UI related. Especially writing tests for the business logic was not a trivial task.

That’s why I chose to come up with a setup that enables a proper test driven development (TDD) workflow in VS, which helps me to develop code that still compiles in iX Developer, and to document the setup in this blog post.

My experience with this setup is specific to Beijer iX Developer. However, parts of this post could be helpful for similar platforms using .NET CF as well. This is also by no means the ultimate guide to either working with iX and VS or for TDD. I drew inspiration from Jeff’s YouTube video and found a setup that works well for me. I am happy about any suggestions on how to improve it!

I blabber a bit about motivation and a few details, which I highly recommend if you are going down this route because there are some quite frustrating pitfalls. But you can also jump straight ahead to this example project on GitHub.

Advantages of Visual Studio over iX Developer

While the preferred development methodology for any pair of two software projects might look quite different, it is difficult to deny the advantages of being able to test an implementation reliably. The importance of testability only increases in the context of embedded and potentially safety critical software. If one’s Beijer panel is doing more than simply displaying a few sensor values, the VS test explorer alone should be motivation enough to look into a possible setup using VS.

For building the UI and wiring up the interface to the business logic, one should still use iX Developer - this is what it shines at. The setup documented in this blog post requires some interaction with iX Developer, even for the business logic parts. However, after an initial effort to define the interface the business logic uses to communicate with the UI, it only needs to be opened occasionally. (One of these occasions being to compile and publish the project.)

On top of the test explorer, VS comes with a few more treats, such as:

There is a good chance that you are eligible for the free VS community version, which has all the functionality you need, or you are already paying for it anyways.

Test driven development approach

This blog post is no introduction to TDD, but for those new to it, the basic concept goes something like this:

To add more requirements, simply add new test cases and repeat the steps above. The pattern is called: Red β†’ Green β†’ Refactor. With this approach one is less likely to implement any unneeded functionality and does not have to be afraid of refactoring the implementation. As long as all test cases are all green, nothing can break. It’s also a very rewarding process since one is constantly returning to a state where everything is working fine and it is easy to backtrack to it. On top of that, where a test case is failing, there is always input data for that scenario, to debug it with. Of course in the beginning everything will go a bit slower.

Robert C. Martin gives an excellent introduction to and motivation for TDD in this presentation. For a shorter example check out this YouTube Video.

(If you are still clueless what the post thumbnail is referring to, check out this post. πŸ˜‰)

Setup a Visual Studio project for compact framework development

The biggest pitfall when developing for iX in VS and then compiling the project in iX Developer is to not setup the VS project correctly and ending up using features (some of them are listed in the end of this post) and libraries (such as more recent versions of Json.NET ), which are not available for .NET CF. Officially, support for .NET CF development ends with VS 2008 but there is a way to configure the project for later versions as well. I followed this setup guide to configure my project in VS 2019 without any problems. Apparently the download of Power Toys, linked in that post, is not available anymore. Web archive to the rescue. 🦸

Project creation and structure

At this point I assume that the .Script.cs files are no entirely new concept, as we will be using these to write our business logic in. There are a few caveats when it comes to creating and structuring the script files, that I have encountered so far and will explain in this section.

I created the border-patrol example that outlines the project structure I am using. The example was written using a TDD approach and the test cases follow the NUnit naming scheme, I documented in an earlier blog post. The repository also includes a .gitignore for the project structure that I outline below and would recommend copying when using a version control system (which you should!).

First of all, new script files must be created in iX Developer, as there are two files that will be created alongside the [script name].Script.cs file. Once the script files are created, I recommend to close iX Developer immediately, to reduce the risk of accidentally making changes and overriding the progress from VS. Just open it again to make changes to the interface between the UI and the business logic or the UI itself.

In VS we simply create a new solution with two projects inside. One for links to the source code files, which we create with the Add -> Existing Item... option in the project menu, and one for the test project, where we create our test files. iX Developer does not know how to handle these anyways so it’s fine that these are fully contained in the VS solution.

For the border-patrol example it looks as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
Solution 'BorderPatrol'
 πŸ—„οΈ BorderPatrol Project
  πŸ”— Model.Script.cs
  πŸ”— WalkerStructure.Script.cs
 πŸ§ͺ BorderPatrol.Tests Project
  πŸ“ Model
   πŸ“„ Canvas/WhenDrawingCanvas.cs
   πŸ“„ Rectangle/WhenDrawingRectangle.cs
   πŸ“„ Square/WhenDrawingSquare.cs
  πŸ“ Walker
   πŸ“„ Helpers.cs
   πŸ“„ WhenUpdatingGrid.cs
   πŸ“„ WhenWalking.cs

In the example above, it might look a bit strange that there is a Model.Script.cs file but the tests are split up into several directories. Model.Script.cs actually contains the classes (Canvas, Rectangle and Square) and Model is just the namespace. This file structure is not ideal, but necessary in iX Developer because the .Script.cs files result in some generated classes that prevent us from creating the class Canvas in the file Canvas.Script.cs. During compilation, iX Developer generates a partial class with the prefix for the file name that among other functionality has access to the tags. We use this functionality for the wrapper that connects our business logic to the UI but have to pay with basically loosing one level in our file tree.

I found it quite annoying when I first found out why this was failing all the time but have now accepted that it is like this. Maybe someone with more insight in how iX Developer actually compiles all the sources can suggest where to put the pure business logic instead of the .Script.cs files.

Additionally, all classes must be named differently from the namespace and decorated as internal, which requires us to add this line on top of the namespace. At the time of finishing this post, I cannot recollect what this was caused by.

1
2
3
4
5
6
7
[assembly: InternalsVisibleTo("BorderPatrol.Tests")]
namespace Scripts.Model {
	internal class Canvas {
		...
	}
	...
}

I store the VS solution on the same directory level as the iX files:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
πŸ“ border-patrol
 πŸ“ iX
  πŸ“„ BorderPatrol.neoproj
  ...
 πŸ“ vs
  πŸ“„ BorderPatrol.sln
  πŸ“ BorderPatrol
   πŸ“„ BorderPatrol.csproj
  πŸ“ BorderPatrol.Tests
   πŸ“„ BorderPatrol.Tests.csproj
   πŸ“ Model
    ...
   πŸ“ Walker
    ...

Interface between UI and business logic

The content displayed in UI components can be set using tags. These are difficult to access outside of iX Developer.

Tags are the interface between the UI and the business logic. To access the tags from the business logic, there are two basic approaches.

  1. Implement the entire business logic stateless (functional) and build a wrapper around it that pragmatically calls these functions, passing the tag values and updating tags based on the return values.
  2. Create a slim wrapper, that wraps the tags in VariableReferences so that the business logic can update them directly.

The border-patrol example is using a combination of the two in the WalkerModule. Notice that this class is actually public and partial so that it aligns with the class that iX Developer partially generates.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// WalkerModule.Script.cs
public partial class WalkerModule {
	static WalkerTags walkerTags = new WalkerTags(
		new VariableReference<int>(
			// XValue
			() => Globals.Tags.XValue.Value,
			val => { Globals.Tags.XValue.Value = val; }),
		new VariableReference<int>(
			// YValue
			() => Globals.Tags.YValue.Value,
			val => { Globals.Tags.YValue.Value = val; }),
		new VariableReference<string>(
			// RenderOutput
			() => Globals.Tags.RenderOutput.Value,
			val => { Globals.Tags.RenderOutput.Value = val; }),
		new VariableReference<bool>(
			// Running
			() => Globals.Tags.Running.Value,
			val => { Globals.Tags.Running.Value = val; })
		);
	
	Walker walker = new Walker(walkerTags);
	
	public void Loop_1s() {
		walker.Walk();
	}

	public void UpdateGrid() {
		walker.UpdateGrid();
	}
}

VariableReference

I created the class VariableReference that has a Get and Set method to which I can then map the respective functions of the tags.

1
2
3
4
5
6
7
8
9
internal class VariableReference<T> {
	public Func<T> Get { get; private set; }
	public Action<T> Set { get; private set; }

	public VariableReference(Func<T> getter, Action<T> setter) {
		Get = getter;
		Set = setter;
	}
}

Using this class one can then create a class containing all the tags needed in the business logic.

1
2
3
4
5
6
7
8
9
10
11
12
internal class MyBusinessLogicTags {
	public readonly VariableReference<string> SomeTagName;
	public readonly VariableReference<bool> SomeOtherTagName;

	public MyBusinessLogicTagNames(
		VariableReference<string> someTagName = null,
		VariableReference<bool> someOtherTagName = null
	) {
		SomeTagName = someTagName;
		SomeOtherTagName = someOtherTagName;
	}
}

During testing, this helper class can be used to instantiate a tag container that can be setup before each test case to simulate the desired state.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Helpers {
	class TagContainer {
		public string someTagName;
		public bool someOtherTagName;

		public TagContainer() {
			someTagName = "";
			someOtherTagName = false;
		}
	}

	public static MyBusinessLogicTags CreateTags() {
		var tagContainer = new TagContainer();

		return new MyBusinessLogicTags(
			someTagName: new VariableReference<string>(
				() => tagContainer.someTagName,
				val => { tagContainer.someTagName = val; }),
			someOtherTagName: new VariableReference<string>(
				() => tagContainer.someOtherTagName,
				val => { tagContainer.someOtherTagName = val; })
		);
	}
}

Limitations of Beijer iX over .NET CF

Even after setting up the project as described above, there are a few particularities to avoid altogether or at least be aware of, when opening iX Developer again. Some of these will break the build in iX Developer, even though the project compiles fine in VS.

Auto properties

Auto properties in iX Developer only work as long as both get and set are specified. In practice one might want to use just { get; } to create a readonly property but iX Developer won’t compile that.

1
2
3
4
class Rectangle {
	public int A { get; set; }
	public int B { get; }
}

Named parameters

When using named parameters, iX Developer complains about them but compiles the project just fine. 🀷 So when injecting the VariableReferences for the tabs into the business logic, we cannot work with the constructor parameter names but have to fully rely on the order…

1
2
3
class Square: Rectangle {
	public Square(int a) : base(a = a, b = a) { }
}

Expression-bodied members

I noticed that iX Developer won’t compile with expression-based members like this one:

1
2
3
4
5
class Rectangle {
	public int A;
	public int B;
	public int Area => A * B;
}