Broadly, you can segregate your tests into function/integration testing and unit testing. Functional testing can he achieved using Arena Test Framework and for unit testing you can either choose the built-in unittest library or any third party library. I have used pytest myself and I am quite satisfied with its concept and capabilities, so I will be talking about pytest in the blog.
Functional/Integration Testing
If you do not have any test framework in place already, you must go with Arena Test Framework without further thought. Its a nice framework with a dashboard to view tests and results. Business users can define a test case in plain English text or provide an excel in a way that ATF developer can implement them. I would not recommend assigning one of your developer to build ATF tests as a partial responsibility. It has some learning curve, though not very steep. Tests can get relatively complex and maintaining them may require constant effort. One possible approach is to hire an FIS consultant for a short duration to have the test cases built. Maintenance of those tests can be managed internally. ATF might rely on some existing static data in the system, so I suggest you have a separate test environment and not combine it with dev.
Unit Testing
Choice of framework
Two frameworks that are popular among python developers are unittest and pytest. Choice among these will largely depend on how your source code is structured.
If your code resides in ADS and you use Front Arena Transporter to export extension modules, you need to stick to unittest. As you cannot use external editors and tools, you are better of using default unittest library. You will write and run tests from within Prime.
I have seen Front Arena users who have build sophisticated import/export tools that export individual extension types to disk. Since FPythonCode is available as py files on the disk, they can leverage external tools like PyCharm and PyScripter for editing and also take advantage of frameworks like PyTest for unit testing.
I am a fan of Object Oriented paradigm. But when it comes to the choice of unit testing framework, I prefer pytest which follows procedural/functional paradigm, over unittest which follows OO paradigm. I am going to spend more time in this section explaining some best practices and pitfalls to avoid. I will be using pytest but you should be able use this knowledge with any other framework of your choice.
Test using mocked FObjects – preferred
Say you have a AuthorizeSettlement function defined below that you intend to test.
def authoriseSettlement(settlement):
if settlement.Amount() < 100000 and settlement.Trade().Status() == 'BO-BO Confirmed' and settlement.Party().Type() == 'Client':
settlement.Status('Authorised')
settlement.Commit()
To test this function, you must create a party, a trade and associate them with a settlement. Rather than create real FObject, I recommend using mocker to mock them. Here is how our test function will look like:
def test_AmountLessThanTenThousand(mocker):
# Prepare objects
settlement = mocker.MagicMock()
trade = mocker.MagicMock()
party = mocker.MagicMock()
settlement.Amount.return_value = 100
settlement.Trade.return_value = trade
trade.Status.return_value = 'BO-BO Confirmed'
settlement.Party.return_value = party
party.Type.return_value = 'Client'
# setup spies who will make sure Status and Commit functions
# were triggered on the settlement
status_spy = mocker.spy(settlement, 'Status')
commit_spy = mocker.spy(settlement, 'Commit')
# Here we make real function call
authoriseSettlement(settlement)
# Now comes the testing part
status_spy.assert_called_once_with('Authorised')
commit_spy.assert_called_once_with()
By mocking, I can test all possible edge cases. In fact, creation of mocked FObjects can be moved to fixtures so multiple tests can reuse them.
Testing using not-so-real FObjects – not preferred
By not so real objects I mean objects created in memory but aren’t committed. Even though I would not recommend testing using FObjects, if you still want to go ahead, there is one API you must be aware of and that is RegisterInStorage. To understand why this API is so important for unit testing, lets take an example:
def addPaymentAmounts(trade):
return sum([payment.Amount() for payment in trade.Payments()])
Our example function takes trade as input and returns sum of amounts of all payments. To test this function, we must create a trade object, payment objects and associate these payments with trade.
test_add_two_payments():
trade = acm.FTrade()
payment1 = acm.FPayment()
payment1.Amount(100)
payment1.Trade(trade)
payment2 = acm.FPayment()
payment2.Amount(100)
payment2.Trade(trade)
total = addPaymentAmounts(trade)
assert total == 200
This test will fail! Reason is, trade.Payments() will return an empty list. Since the objects are not being committed, there is no way to establish a relationship between them. This is where RegisterInStorage comes to rescue. Lets update our test function :
test_add_two_payments():
trade = acm.FTrade()
payment1 = acm.FPayment()
payment1.Amount(100)
payment1.RegisterInStorage()
payment1.Trade(trade)
payment2 = acm.FPayment()
payment2.Amount(100)
payment2.RegisterInStorage()
payment2.Trade(trade)
total = addPaymentAmounts(trade)
assert total == 200
Now the test should pass. RegisterInStorage registers objects in Prime’s local cache, as if they were fetched from database. This way, it is possible to connect objects with each other. In general, it is a good idea to register all objects when unit testing. Also, you must move object creation responsibility to fixtures to avoid code duplication.
Testing using real FObjects – absolute NO!
By real FObjects I mean objects either fetched from ADS or committed during test setup. I am not going to show any examples, since it is absolute NO from me.
Testing python code from local repository
Default export of FPythonCode extension generates a txt file that combines all python modules under that extension. This exported txt file is neither unit test friendly, nor human friendly. I wish and also suggest FIS to have a tool that can export more granular components. For example, export FPythonCode as separate python modules.
FIS Transporter does export python modules from Python Editor as py files. So in this section, I will talk about how those python modules can be tested.
After you modify python code, it isn’t a good setup if it requires you to upload python modules into test environment to be able to test them. By uploading untested modules, chances are you might break things. So the idea is to load all your modules from disk before you make acm connection. But there certain things you must take care of to be able to achieve this.
- You must run tests either from command line or code editor. Load all python modules from disk and then connect to ADS using acm.Connect() API.
- Modules dependent on FUxCore cannot be imported without established acm connection, so make sure you keep business logic separate from UI code.
Conclusion
Unit testing cannot be an after thought. It never works that way. Unless the code is built with unit test in mind, you wont be able to test it. For example, it is quite hard to test code that has objects instantiated inside the business logic. You must delegate object creation to a factory and follow dependency injection. This way you will be able to easily mock the dependencies. As long as your code follows SOLID principles, you should be good.
Building a robust software guarded with unit and integration tests takes time. Product owners, Project managers and department heads in general should be aware of this fact and factor these things in project/delivery planning. They must support and encourage developers in building well crafted and fully tested solutions. Once it becomes a practice, this approach will save you tons of time and money.
0 Comments