In order to test FME you can use python and fmeobjects. You can inject a certain geometry with specific attributes with just a few lines of python code, and of course we can run it using the behave library or your favorite test framework. In the following example, we check the geometry if it has a Z value, and if this Z value is between certain boundaries. The following example is in dutch, but should be fairly readable for non native speakers.
check_punt_geometry.feature
# language: nl Functionaliteit: Check punt geometrie Afsluitmiddel Punten (punt x,y,z) liggen tussen de drempel waardes en hebben 3 dimmensies Scenario: Afsluitmiddel punt heeft geen Z-waarde Gegeven de testset bevat "CHECK_3D_POINT" En heeft de volgende geometrie """ POINT (10 20) """ En de drempel waardes voor Z liggen tussen 7 en 300 En het attribuut "meta_feature_type" is "Afsluitmiddel" En het attribuut "OBJECTID" is "55017666" Als de check uitgevoerd wordt voor Afsluitmiddel Dan staat de volgende melding in het log bestand """ WARN | Afsluitmiddel (ObjectID: 55017666) heeft geen Z waarde. #1005 """ Scenario: Afsluitmiddel punt heeft een geometrie met een Z waarde tussen de drempel waardes Gegeven de testset bevat "CHECK_3D_POINT" En heeft de volgende geometrie """ POINT Z (10 20 30) """ En de drempel waardes voor Z liggen tussen 7 en 300 En het attribuut "meta_feature_type" is "Afsluitmiddel" En het attribuut "OBJECTID" is "55017666" Als de check uitgevoerd wordt voor Afsluitmiddel Dan staan er geen warnings in het log bestand Scenario: Afsluitmiddel punt heeft een geometrie met een Z waarde onder de drempel waardes Gegeven de testset bevat "CHECK_3D_POINT" En heeft de volgende geometrie """ POINT Z (10 20 0) """ En de drempel waardes voor Z liggen tussen 7 en 300 En het attribuut "meta_feature_type" is "Afsluitmiddel" En het attribuut "OBJECTID" is "55017666" Als de check uitgevoerd wordt voor Afsluitmiddel Dan staat de volgende melding in het log bestand """ WARN | Afsluitmiddel (ObjectID: 55017666) de Z waarde ligt buiten de drempel waardes (minimum: 7.0, maximum: 300.0). #1005 """ Scenario: Afsluitmiddel punt heeft een geometrie met een Z waarde boven de drempel waardes Gegeven de testset bevat "CHECK_3D_POINT" En heeft de volgende geometrie """ POINT Z (10 20 400) """ En de drempel waardes voor Z liggen tussen 7 en 300 En het attribuut "meta_feature_type" is "Afsluitmiddel" En het attribuut "OBJECTID" is "55017666" Als de check uitgevoerd wordt voor Afsluitmiddel Dan staat de volgende melding in het log bestand """ WARN | Afsluitmiddel (ObjectID: 55017666) de Z waarde ligt buiten de drempel waardes (minimum: 7.0, maximum: 300.0). #1005 """steps.py
from behave import * from sys import path path.append(r"C:Program FilesFMEfmeobjectspython36") from fmeobjects import FMEFeature, FMELogFile from src.checks import * import unittest from unittest.mock import patch def test_macroValues(self, mock_macroValues): mock_macroValues.return_value =3D 7 @given(u'het attribuut "{key:w}" is "{value}"') def step_impl(context, key, value): if not hasattr(context, "input"): context.input =3D FMEFeature() context.input.setAttribute(key,value) @when(u'de check uitgevoerd wordt') def step_impl(context): context.logger =3D FMELogFile() context.logger.holdMessages(True) processFeature(context.input) context.logger.holdMessages(False) @then(u'staat de volgende melding in het log bestand') def step_impl(context): heldMessages =3D context.logger.getHeldMessages() heldWarning =3D next((message for message in heldMessages if message.s= tartswith("WARN")), None) assert context.text.rstrip() =3D=3D heldWarning @then(u'staan er geen warnings in het log bestand') def step_impl(context): heldMessages =3D context.logger.getHeldMessages() heldWarning =3D next((message for message in heldMessages if message.s= tartswith("WARN")), None) assert heldWarning =3D=3D None @when(u'de check uitgevoerd wordt voor afsluitmiddel') def step_impl(context): context.logger =3D FMELogFile() context.logger.holdMessages(True) processAfsluitmiddel(context.input, context.testset) context.logger.holdMessages(False) @given(u'heeft de volgende geometrie') def step_impl(context): if not hasattr(context, "input"): context.input =3D FMEFeature() context.input.importGeometryFromOGCWKT(context.text) @given(u'de drempel waardes voor Z liggen tussen 7 en 300') def step_impl(context): passsrc/ check.py
from enum import IntEnum, unique import fme from fmeobjects import FME_WARN, FMELogFile @unique class Seed(IntEnum): ... CHECK_3D_POINT = 1005 CHECK_3D_LINE = 1006 CHECK_3D_POLYGON = 1007 ... def processFeature(feature, logger = None, seeds = frozenset(range(1000,9999))): if logger is None: logger = FMELogFile() logger.logMessageString("Kunstwerkdeel check gestart.") ... logger.logMessageString("Kunstwerkdeel check uitgevoerd.") def processAfsluitmiddel(feature, seeds = frozenset(range(1000,9999))): logger = FMELogFile() logger.logMessageString("Afsluitmiddel check gestart.") meta_feature_type = feature.getAttribute("meta_feature_type") object_id = feature.getAttribute("OBJECTID") ... process3DPoint(feature, meta_feature_type, object_id, logger, seeds) logger.logMessageString("Afsluitmiddel check uitgevoerd.") def process3DPoint(feature, meta_feature_type, object_id, logger = None, seeds = frozenset(range(1000,9999))): if logger is None: logger = FMELogFile() try: minZ = float(fme.macroValues["Geometrie_MinimaleZ"]) maxZ = float(fme.macroValues["Geometrie_MaximaleZ"]) except AttributeError: minZ = 7.0 maxZ = 300.0 if Seed.CHECK_3D_POINT and feature.hasGeometry(): if feature.getDimension() == 2: logger.logException(ValueError(f"{meta_feature_type} (ObjectID: {object_id}) heeft geen Z waarde. #{Seed.CHECK_3D_POINT}"), FME_WARN) elif not(minZ <= feature.getAllCoordinates()[0][2] <= maxZ): logger.logException(ValueError(f"{meta_feature_type} (ObjectID: {object_id}) de Z waarde ligt buiten de drempel waardes (minimum: {minZ}, maximum: {maxZ}). #{Seed.CHECK_3D_POINT}"), FME_WARN) processFeature(feature, logger, seeds)Running this against behave gives the following output:
Functionaliteit: Check punt geometrie # features/check/3010_afsluitmiddel/check_punt_geometrie.feature:2 Afsluitmiddel Punten (punt x,y,z) liggen tussen de drempel waardes en hebben 3 dimmensies En heeft de volgende geometrie # features/steps/steps.py:74 0.793s """ POINT (10 20) Dan staat de volgende melding in het log bestand # features/steps/steps.py:25 0.000s """ WARN | Afsluitmiddel (ObjectID: 55017666) heeft geen Z waarde. #1005 """ En heeft de volgende geometrie # features/steps/steps.py:74 0.803s """ POINT Z (10 20 30) Dan staan er geen warnings in het log bestand # features/steps/steps.py:31 0.000s En heeft de volgende geometrie # features/steps/steps.py:74 0.830s """ POINT Z (10 20 0) Dan staat de volgende melding in het log bestand # features/steps/steps.py:25 0.000s """ WARN | Afsluitmiddel (ObjectID: 55017666) de Z waarde ligt buiten de drempel waardes (minimum: 7.0, maximum: 300.0). #1005 """ En heeft de volgende geometrie # features/steps/steps.py:74 0.800s """ POINT Z (10 20 400) Dan staat de volgende melding in het log bestand # features/steps/steps.py:25 0.000s """ WARN | Afsluitmiddel (ObjectID: 55017666) de Z waarde ligt buiten de drempel waardes (minimum: 7.0, maximum: 300.0). #1005 """This way, you can automaticly verify your FME workbench without relying heavily on the available test sets.
Things to improve are the ugly try ... except AttributeError: block to inject a macroValue into the test, possible solution for this is creating a mock.
Also a next step might be testing this on a server, while I have been able to run a docker instance of FME server, I have yet to discover where the fmeobject is located so I can run this test.
Also obviously, you need an FME license for this, so we need to figere out how to inject the licence key to get the code to work in a secure way.
So, you might ask, can we extend this to also test our workbench. Actually, that is super easy and barely an inconvenience! According to this knowledge.safe.com blog post, you can automaticly run a workbench from the python API, hooking this up to a Given step in behave is trivial, you could also check if certain links exists using a textual graph parser in the dot notation for example. I leave this as an exercise for the reader to implement.