2

I'm new to TDD and ATDD, and I'm seeking to understand the connection between a user story and its acceptance criteria. For context, I'm building a 3-tier application in C#, with an MVC front-end.

Say, for instance, that I have the following user story:

In order to ensure Capacity Data is entered correctly As a person updating this data I want feedback when the data entered doesn't conform to our business rules.

It makes sense to me to break this down and define what "Capacity Data" is, and the business rules that govern this.

For instance, maybe it has a "Number of Machines" property that has to be greater than zero.

What I want to avoid doing is testing the framework--and if I follow correctly, what I want to do is test that this business logic is correctly implemented, i.e. that:

  1. Business rules ("Number of machines must be greater than zero", and others) are correctly implemented in the codebase.
  2. If a business rule is violated, the user is alerted of this mistake.

I believe I could test rule #2 by validating that invalid model state in the controller redirects to the same page, for instance--and there's TONS of examples of that.

However, doesn't doing this require putting decorations on the viewmodel--and that ultimately this implements the business rule from the perspective of the user? (thus satisfying #1?)

Let's say I have the following sort-of statement/unit-test:

[Test]
public void GivenCapacityModelWhenNumMachinesZeroOrLessThenModelShouldBeInvalid()
{
   // Given
   IValidatorThing validator = new ValidatorThing(); //What enforces the rule? Should this be a controller service? Or a decorator such as [Range(0.000001,1000000)]? Doesn't each require different testing methods?
   var invalidModel = new CapacityModel(); // Or the viewmodel?
   double zeroValue = 0.000001;
   invalidModel.NumMachines = zeroValue;

   // When
   var modelIsValid = ValidatorThing.validateModel(invalidModel); 

   // Then
   Assert.IsFalse(modelIsValid);
}

The above won't compile, of course. I've left out any particular mocking or fixturing framework out for now, to keep it simple. So, to make this test at least compile (but still fail), I have some decisions to make:

  1. Is CapacityModel supposed to be a viewmodel? Or a DTO from the service layer? Or a metadata class in the DAL layer? I can implement either and make the test pass...but what should I really be testing?
  2. Is the "validator" checking the behavior of a service that validates this model property? Or data annotations on the CapacityModel? Again, what should I really be testing in the context of a 3-tier application?

Some things to consider: One thing I will know is that the database tables will have constraints that describe these rules--so it would seem that the purpose of this rule is really to communicate these rules to whoever is using the application. In that case, could I safely assume it would violate DRY to have have the rules appear in three places: the viewmodel, data entity, and database tables.

The reason we have these rules in the database is because we want to ensure if a DBA needs to mess with the records that the rules aren't accidentally violated. However, to my knowledge there isn't a great way to translate those CONSTRAINT rules up to the DAL of the application...so I suppose they would need to be repeated at least one more time in the application for sake of communicating them to the user.

So, if I were to write a unit test to fulfill the business rule wouldn't I be writing only to ensure the rules mirror the database? And separately, also writing a unit test that ensures the correct message is displayed to the user?

Any guidance you can offer would be wonderful. I want to feel that the decisions I've made were reasonable, or at least have an idea of alternative ways to solve the problem better.

Thanks,

Lawrence

EDIT:

So, ultimately I was trying to drive at a centralized way of managing validation in the application so that I could have separation of concerns--i.e., that the controller only cared about routing, the viewmodels only cared about displaying data, validators only cared about validation, etc...as opposed to having validation in the viewmodel, for instance.

I found a very helpful article that helped me to grasp how to do this, using the existing MVC infrastructure. Hopefully this will help others looking into these types of scenarios.

Chris
  • 71
  • 5

1 Answers1

2

I suspect you may be blurring the boundary between unit tests and acceptance tests.

An acceptance test is something that is very business user focused. It treats the application as a black box but checks to confirm that the interface to the application behaves in the way the user would expect it to.

In your example I would see an acceptance test as something like:

For a simple business rule (number of machines must be greater than zero), ensure that correct feedback is given to the user in the event of the business rule being violated.

I would have a chat with the Product Owner at this stage to understand what they regard as 'correct feedback' and how they want it to be displayed.

The important thing here is that you are not testing how business rules are evaluated or what the internal mechanism is for handling errors. You are purely focused on the end-user interaction.

Of course you will also want to implement unit testing to ensure that your technical solution is solid and this is where you go in to details about where business logic is implemented.

How you handle business logic is very much a design decision. Personally, if I had the business logic in the database I would also have a table containing rule descriptions that would be used as a look-up in the event of a rule being violated. The other layers of the application would know nothing of the business logic, but would know how to pass through the error message.

Barnaby Golden
  • 4,176
  • 1
  • 23
  • 28
  • Thanks! This does help to confirm I am mixing up two different concepts. For context, I'm reading [this](https://leanpub.com/tdd-ebook/read#leanpub-auto-the-essential-tools). So, established that "For a simple business rule (number of machines must be greater than zero), ensure that correct feedback is given to the user in the event of the business rule being violated." is an acceptance test, then I would decompose this into multiple unit tests? For instance, "Should display 'invalid number' when X<0", "Should display 'required' when Y is blank"? – Chris Dec 05 '15 at 23:37
  • To clarify on the solution you suggested, theoretically would I be making a call to a database table (containing error codes) after an error message? Or somehow catching the error thrown by Oracle? [this thread](http://stackoverflow.com/questions/6068792/is-there-way-to-give-user-friendly-error-message-on-constraint-violation) discussed catching errors from Oracle, but it doesn't sound like something doable in practice. Can you advise on resources that describe an implementation in detail? (having trouble searching for these, there doesn't seem to be much information--maybe you know of some?) – Chris Dec 05 '15 at 23:51
  • I would expect you to be making a call to get the error message. If you could trigger it from the original Oracle error that would be great, but I've never used such an implementation. Sorry, but I don't have any resources that describe this kind of implementation. On your first point: I would expect an acceptance test to test the mechanism of the error message and not every possible error condition. But that is just down to my views on efficient use of test coverage. If you want your coverage to test every condition then go for it! – Barnaby Golden Dec 06 '15 at 09:19
  • Thanks for your feedback, Barnaby. I suppose it would be nice if I could trigger the error from Oracle, but in the end if the rules themselves will be done in the database then there is little value provided in testing them again in the application. I'm thinking it would make more sense to ensure that--from the user perspective--they are "alerted" in some way before POSTs ("field is required" stuff) if required and after POSTs (maybe using [HandleError("some error page")] for exceptions. This way, the db adds value with CONSTRAINTs, and the app adds value with user messages. – Chris Dec 06 '15 at 19:09