I've inherited an application that has a suite of tests that drive me batty. But one of the design decisions that this test suite takes that completely leaves me scratching my head is the separation of tests and their expectations into separate files.
To explain, there are functional tests that have test methods with decorators. The tests themselves do the setup, run the test procedure, and any teardown if necessary. But the expectations for the tests are defined in separate JSON or YAML files. The decorators on the test methods do some magic to find the expectations file, and the appropriate data set to match the test method.
This pattern is also used for input values and fixtures. I find this overly designed, but I can maybe understand input values and fixtures being external.
This drives me nuts because if I have to make a logic change, I have to search through multiple files to find the expected values and change them. I've wasted days trying to fix the test suite after any major refactoring is made.
I just want to know if there is any valid reason for this design pattern. I can't think of one. If someone has suggestions or information on why this pattern was chosen or possibly prescribed, please add links to appropriate articles, docs, and opinions.
--- EDIT ---
Sorry to have left this question hanging.
I realize I didn't give a lot of background. So let me give you more. I've heard arguments that there are situations where non-developers (ie project managers, accountants, etc...) might need to edit tests or expectations. This is not the case. These tests are only to be written, read, and executed by developers and/or automation. I also heard that it could be the case with BDD, where you might use simple nouns to be placeholders for expected values. This isn't the case either, we don't have any BDDs, and whether or not they are useful is irrelevant to this question. As stated, they are supposed to be functional tests. There are unit tests, but those are more like integration tests, they test a full path of execution.
The project's language is Python and the tests are nose tests. The application is a web service API. No browser front end, it is designed to interact mostly with our client SDK's (mobile and various device platforms). Our customers are primarily developers of mobile games.
Here is an example of how the tests are laid out:
project/
tests/
functional/
api/
expected_values/
anonymous_user_test_case.json
...
input_values/
anonymous_user_test_case.json
...
anonymous_user_scenario_test.py
The JSON files in expected_values
and input_values
each contain a single JSON object that contains an object for each test.
Here's an example of an input_values
file. These files contain test data representing request data that would be posted to the API from the SDKs. The first object default
represents the version of the API that is being tested. This I think is unnecessary because we don't have very many versions, and the ones that are, are drastically different.
{
"_register": {
"default": {
"json_data": {
"device": {
"identifier": "dev_1",
"name": "testuser device 1"
}
}
}
}
// ...
}
Then the expected_values
files contain the values a test expects to be returned in the API response. Each file is one object that contains an object for each test.
{
"_register": {
"default": {
"created_at": "regex:DATETIME",
"display_name": null,
"email": null,
"is_confirmed": false,
"state": "anonymous",
"uuid": "regex:UUID"
}
},
// ...
}
And here is what that test would look like.
""" imports and such """
class AnonymousUserTestCase(base.BaseFunctionalTest):
def setUp(self):
super(AnonymousUserTestCase, self).setUp()
self.resp = None
self.session_id = None
self.email = None
""" OTHER TESTS """
@check_expected
@with_input_data
def _register(self, json_data=None):
"""Test register user."""
path = self.populate_api_version('/{version}/auth/register')
resp = self.app_post_json(path, data=json_data)
self.email = resp.parsed_data.get('username')
self.uuid = resp.parsed_data.get('uuid')
return reps
""" MORE TESTS """
Still to this day I have to live with these tests. And because of them, testing has been problematic. We've taken a different approach to testing this app. It's more of a Blackbox test. We have a dev environment set up, and a Simulated device then makes request calls to the API and checks for the expected responses. This makes it easier for the Client SDK developers because they can see and control how the API is used and if it meets their expectations. But it does cause some longer turnaround time because it requires the services developers to make the API changes, deploy it and then run the automated testers. Usually, by that time, the Client SDK developers have already written the tests.
We are only now performing minor updates and hotfixes to this API and hope to replace it completely with a new project.