In my earlier posts I’ve written about development of TwinCAT software using test driven development (TDD), by writing unit tests. One of the advantages by adhering to the process of TDD is that you mostly will end up with function blocks (FBs) which have limited but well defined responsibility. Eventually you will however have FBs that are dependent on other function blocks. These could be FBs that are your own, or part of some 3rd party library, for example a Beckhoff library. Further, what if this external FB relies on some other functionality such as external communication using sockets that we have no control of? The external FBs should already be tested, we’re only interested in making sure our unit tests test our code! What do we do? A solution to this is to mock the external functionality and use dependency injection.
The technique of mocking objects is a way to abstract the external functionality and do a dependency injection of the concrete implementation of that into your function block. Huh, what did I just say? Let me explain with a few illustrations. Lets say you’re developing a function block (FB) that is supposed to deliver some results (output) based on some inputs. These inputs can be any data provided in the classical sense of VAR_INPUT, but also by a dependency from an external system by using an external FB that does some communication to this system. The conventional way of solving this is to create an direct dependency of this external FB by creating an instance of it in your FB.
This is the most obvious and straightforward way of implementing dependencies to function blocks, but this causes one big problem – it’s not possible to write a unit test for your FB, because there is a direct dependency to something that we have no way of dealing with in our unit test. More importantly, FB_External is an external library and should already be tested.
The solution to our problem is to mock out the function block FB_External by something well known in the OOP-sphere called dependency injection. With dependency injection we supply the external dependency to our FB instead of our FB creating an instance of it. Now how is this “mocking” going to help us? The magic trick here is that we can create a dummy FB_External that has some pre-defined behaviour, and in our unit-test we inject this dummy instead of the real deal! Think of it this way: our function block FB_OurFB doesn’t care the slightest of how FB_External works, it just wants to have access to the service/functionality (whatever that may be) that the FB provides. If we mock this object and provide FB_OurFB with a dummy object that gives a pre-defined output, we effectively remove the direct dependency to FB_External in our unit test, and we can focus to test what is important, which is the internals of FB_OurFB.
In practice there are a few ways to accomplish this in IEC61131-3. One way that resembles how you would do it in other languages such as C++ is to provide the dependency in the constructor of the object. The FB_init method in TwinCAT3 is what most closely resembles a constructor in IEC61131-3, and it’s in this method that we can inject our dependency to the external FB. For this one could provide a pointer to the type of FB_External, but the OOP-way (which is more elegant) is to provide an interface with the functionality provided by FB_External. After all, FB_OurFB only cares about the service that FB_External provides, which is nExternalOutput : DWORD. By utilizing interfaces, we are designing our software in such a way that it’s more obvious of what services and functionality is needed for our FB to do its job. By looking at the constructor (FB_init) of our FB, we can see these dependencies.
In our real application we will inject the concrete function block FB_ExternalWrapper into FB_OurFB, because we want the real deal in the production code. But in our unit tests that are testing FB_OurFB we create a mock object implementing the same interface as FB_ExternalWrapper and inject that instead. We can give this mock object any behaviour that we find suitable for our test. As long as it gives us some pre-defined results we can focus on testing the logic of FB_OurFB.
Woah, the amount of boxes escalated quickly! Before I explain each and one of them, let me explain the overall picture. What we’ve introduced is an interface which gives us the possibility to decide whether we should use a function block or a mock of it. For this to work, we need our FB_OurFB to use the interface instead, and when creating FB_OurFB we need to tell whether it should use the real external FB or a mock of it.
This is an interface that we create which has only one method “execute”. This method has the same inputs and outputs as the FB_External function block, which is the functionality that we want to mock out in our application. Why create a method in the interface and not have these in and out-parameters in the body of the interface? The answer to that is simply that this is not supported in IEC61131-3 (though I wish it was). It’s not possible to create anything such as:
INTERFACE I_External VAR_INPUT bExecute : BOOL; END_VAR VAR_OUTPUT nExternalOutput : DWORD; END_VAR
Try this and the TwinCAT compiler will complain:
As both the function blocks FB_MockFB and FB_ExternalWrapper implements this interface, we can substitute one for the other when having production code versus when running unit tests.
The change we’ve done here is that we’ve introduced the FB_init-method, which is the functionality that allows us to come as close as possible to a constructor as possible in TwinCAT3. With FB_init, we have the possibility to inject a dependency to a function block that implements the interface I_External when we create FB_OurFB. Instead of having a direct dependency to FB_External our function block now instead depends on the interface I_External, for which we can choose whether we want to inject a mock-object or the real deal.
The purpose of the mocking function block is to act as a dummy FB for the real functionality. It’s designed and implemented in such a way that it will return predictable output. The mock FB is replacing some code that already is supposed to be tested and verified. This FB does necessarily not need any internal state – one particular mock object can return the same output value every time. As it implements the I_External interface, we can inject it into the constructor of FB_OurFB.
Because the external function block does not implement our interface, we need to create a wrapper function block around it which implements the I_External interface. The only thing the FB_ExternalWrapper does is to instantiate an instance of FB_External and forward any calls to it through the method execute. This gives us a possibility to use FB_External in the production code by injecting an instance of it into FB_OurFB.
Enough with theory, I’m sure you’re eager to see an example! I’ll demonstrate this concept with an example of using the Beckhoff function block FB_EcCoeSdoRead, located in the Tc2_EtherCAT library. This post has however grown quite a lot compared to what I did foresee, so I will need to split it up into two parts in where the example will go into the next part so as always, stay tuned!