Fixtures are generally used to provide common or centralised test setup. I use fixtures for validation as well. This allows us to remove a dependency on our data stores and to some extent, on our service response structures.
why would we want such an abstraction?
The data store abstraction is useful because as I discussed here on my article about what to focus on in service tests we need to go back to the original source to validate information. If that code is in every test and the data store changes you need to recode every test. A better solution is to get the information you need (as supplied by the service) and pass that to the validation fixture.
If the data store changes then you can rewrite the validation fixture without having to change a single test.
If the service response changes then you are going to change every test anyway but, the scope of your changes are reduced. If you’re using the pipeline then you change your ExtractResult function and the code to pass those values into the validation fixture. This is a usually one-line change in each test (find and replace anyone?) and a pipeline change you had to make anyway.
what will our code look like?
//Result Validation
{
ExtractResult () ;
CheckResultCountEquals (1) ;
CheckAwesomePersonAgainstServiceResponse
(
lr_get ("{Result_AwesomePerson_1_Id}"),
lr_get ("{Result_AwesomePerson_1_Name}"),
lr_get ("{Result_AwesomePerson_1_Address}"),
lr_get ("{Result_AwesomePerson_1_MobileNumber}")
) ;
}
As you can see we have an ExtractResult call as we always do. We verify the result count; this is always a useful check to put in every test that returns a collection. Sometimes you get more than you expect.
Finally, we have our function CheckAwesomePerson. It takes each of the properties from the service request as parameters. This is where we separate the service response from our test. We are just using properties now. If the service gets another property then you will have to update the tests that are impacted but tests that are not impacted won’t need to change.
If for example our additional property was a date/time of the request then we don’t need to validate that in this test; we’ll have it in a separate test focusing on that attribute. Here we are focussing on awesome people.
Next up is the implementation of our CheckAwesomePerson method and this is where we separate our test from the data store implementation. In this case it is d a database.
void CheckAwesomePerson
(
const char* const Id,
const char* const Name,
const char* const Address,
const char* const MobileNumber
)
{
lr_set (Id, "__CheckAwesomePerson_Id") ;
GetRow ("AWESOME_PEOPLE", "ID", lr_get ("{__CheckAwesomePerson_Id}"), "__CheckAwesomePerson") ;
VerifyRowValueEquals ("__CheckAwesomePerson", "ID", Id) ;
VerifyRowValueEquals ("__CheckAwesomePerson", "FOLDER_NAME", Name) ;
VerifyRowValueEquals ("__CheckAwesomePerson", "PARENT_ID", Address) ;
VerifyRowValueEquals ("__CheckAwesomePerson", "FOLDER_TYPE", MobileNumber) ;
}
As you can see we get the row and validate it against the parameters. If the Awesome_People table ended up being moved to an xml-document or a web resource then we can change this implementation and the test themselves won’t need to change.
|
|
Ryan Boucher is a Software Inquisitor and is passionate about it. You can find a whole raft of articles and anecdotes about software testing and other topics he gets excited about. |
| Tags |