-1

Suppose I have the following generic workflow that I'd like to test:

CreateObject --> AddItemsToObject --> SaveObject --> ChangeObjectToFirstState --> ChangeObjectToSecondDependentOnBeingInFirstState --> etc...

Should I:

A) Write a test case for each of those states in their entirety - IE, CreateObject will test whether the object is properly created, AddItemsToObject will use a copy of the code CreateObject used, and then test whether adding items to the object is properly handled, SaveObject will use a copy of the code AddItemsToObject used, and then save the object to the database and test whether it was properly saved, etc...

B) Write the test case for CreateObject, and have a successful result return a created object, which is then used by AddItemsToObject as the object that items are being added to, which is then used by SaveObject as the object being saved, etc...

C) Not bother splitting each step of the chain into separate test cases, and just write a single test case for each complete chain

I've tried searching for other examples that might answer this question to no avail - if it has already been answered thoroughly, please link me the appropriate question :)

* EDIT *

To re-word this a bit so that it's more of a question:

Is there any specific reason not to write your test cases for a workflow using a chain-based approach?

In other words, for a multi-stage workflow, are there any specific reasons not to first call the test case for advancing an object from State #2 to State #3 when implementing the test case for advancing an object from State #3 to State #4?

Kiran Ramaswamy
  • 501
  • 1
  • 5
  • 18
  • Are you describing a call stack or a workflow? – Cleptus Oct 03 '19 at 15:07
  • This is an opinion-based question, and given that I would go for A and possibly a test case for each complete chain as well. The trouble with B is that if something goes wrong and the test fails, you have to walk through the whole chain to see why. – stuartd Oct 03 '19 at 15:09
  • @bradbury9, not exactly sure what you mean - I'm trying to write test cases for a state machine which can be described through a flowchart, essentially. I'm not talking about writing the test cases for the specific states. Rather, I'm asking whether for example, testing the fourth state should first call the test case that brings it to the third state (which, in turn, does the same for the second, etc...), or whether testing the fourth state should instead include the code to bring it to the fourth state. – Kiran Ramaswamy Oct 03 '19 at 15:15

1 Answers1

1

TL;DR - A combination of varying levels of tests, unit (Case A) -> integration (Case B) is the ideal approach.

I've often seen that adding tests to a code base is more of a journey than a single work item in time. A number of variables need to be considered, such as the time allotted for testing, the criticality of the system, the complexity of the code, etc. etc..

Speaking to your example, it is almost always simpler and faster from a developer standpoint to write a broad, integration test (Case C). However, as pointed out, there are downfalls to this approach. To name a couple, 1) If it fails, it will be harder to debug, and 2) If each of your n functions has, for example, 2 code paths, you'd need 2^n test cases to cover all paths. That said, integration/system tests are always better than no tests. And in my experience, they're the most valuable ones to write first when testing an existing/legacy code base.

On the other hand, if you have time, writing unit tests for specific functions (Case A) is an excellent way to supplement your test base. In this case, using the variables outlined above, you'd only need 2*n tests to cover all paths, and a failure in one test would identify a smaller vector for debugging. A good pattern here is to use a DI framework like Moq to isolate logic for each of these individual components.

Here's a more thorough conversation on unit vs. integration tests.

Case B isn't necessarily a bad approach either; it essentially is a form of integration testing where you get a little more insight into where the problem has occurred. Again, these are decisions you'd need to make in the context of your project, taking into account things like the complexity of each individual function in the context of the entire workflow.

chill94
  • 341
  • 1
  • 5
  • Hey @chill94, thanks for the insights. Between Case A and Case B, which do you recommend? Personally, it feels like Case B is the better choice, but I haven't actually been able to find any articles or threads actually recommending it, which is why I'm asking - usually if nobody has asked a question, it means either A) the answer is so obvious that nobody bothered asking, or B) I'm not searching properly :) – Kiran Ramaswamy Oct 03 '19 at 15:28
  • @KiranRamaswamy - Edit: I misread the question - the following compares A and C. Let me add a new comment to answer your *actual* question. ----- I just added a summary point. Ideally, you'd have both. Check out the linked article for more on that. They each have pros and cons, and they each take *time* to write. If time is a major factor, I'd go with Case C, since that will determine if the feature works (essentially). – chill94 Oct 03 '19 at 15:30
  • Using many of the points from my comment above, if time is a major factor, Case B would be the way to go, since it is many ways similar to Case C. You wouldn't need to extract dependencies to get yourself to solid unit tests. That said, again, if you have time, writing solid, *testable* code (and corresponding unit tests - Case A) is always a great investment. But again, I can't make a suggestion since this is a complex decision in practice. The best thing you can do right now is continue to read and learn about testing (just what you're doing!) to continue making well-informed decisions – chill94 Oct 03 '19 at 15:36
  • I read through that stackoverflow thread you linked, and it does give me insights, but it doesn't really answer my specific question. Both Case A and Case B in my example are integration tests - they're not unit tests because, for example, the test case for State 4 doesn't begin with the object in State 3. The reason why it feels like Case B is the better approach, is because in Case A, there's a lot of code being repeated - the test case for State 4 is essentially repeating all of the code that was already written in the test for State 3. – Kiran Ramaswamy Oct 03 '19 at 15:50