Unit tracking state class tracking

I am abstracting the tracking part of the history of my class so that it looks like this:

private readonly Stack<MyObject> _pastHistory = new Stack<MyObject>(); internal virtual Boolean IsAnyHistory { get { return _pastHistory.Any(); } } internal virtual void AddObjectToHistory(MyObject myObject) { if (myObject == null) throw new ArgumentNullException("myObject"); _pastHistory.Push(myObject); } internal virtual MyObject RemoveLastObject() { if(!IsAnyHistory) throw new InvalidOperationException("There is no previous history."); return _pastHistory.Pop(); } 

My problem is that I would like the unit test for Remove to return the last Added object.

  • AddObjectToHistory
  • RemoveObjectToHistory returns what was entered through AddObjectToHistory

However, this is not really a unit test, if I need to add Add first? But the only way I can do this in the true unit test way is to pass the Stack object in the constructor OR make fun of IsAnyHistory ... but taunting my SUT is also odd. So my question, dogmatically, is unit test? If not, how to clean it ... is this the only way to install a constructor? It just seems like you need to go through in a simple object? Can I click on this simple object to insert it?

+6
source share
5 answers

There are two approaches to these scenarios:

  • Join the construction, for example, by creating _pastHistory internal / protected or by inserting a stack
  • Use other (possibly module-tested) methods to verify.

As a rule, there is no golden rule, although I would say that you should generally avoid situations where unit tests force design changes (since these changes are likely to introduce ambiguity / unnecessary questions for consumer coding).

However, in the end, you need to weigh how much you want the unit test code to interfere with the design (first case) or bend the ideal definition of unit test (second case).

I usually find the second case more attractive - it does not clutter up the original class code, and you will most likely try Add - so that it can rely on it .

+4
source

I think this is still a unit test, assuming MyObject is a simple object. I often create input parameters for unit test methods.

I use Michael Perot unit test criteria :

A test is not a unit test if:

  • He is talking to the database.
  • It communicates over the network.
  • It relates to the file system.
  • It cannot work simultaneously with any of your other unit tests.
  • To run it, you need to do special things in your environment (for example, edit configuration files).

The tests that do these things are good. Often they should write, and they can be written in the unit test harness. However, it is important to be able to separate them from true unit tests so that we can maintain a set of tests that we can perform quickly when we make changes.

+1
source

My 2 cents ... how will the client know if he deleted the job or not? How should a “client” interact with this object? Are customers going to insert a stack into the history tracker? Treat the test as a different user / consumer / client of the subject. Using the same interaction as in real production. I have not heard a single rule that you are not allowed to call several methods for the tested object.

To simulate, the stack is not empty. I just call Add - 99%. I would refrain from destroying the encapsulation of this object. Handle objects like people (I think I read this in Object Thinking). Tell them to do things .. do not enter and do not enter.

eg. If you want someone to have money in their wallet,

  • an easy way is to give them money and let them internally put it in their wallet.
  • throw away your wallet and write in a wallet in your pocket.

I like Option1. Also see how it frees you from implementation details (which cause fragility in tests). Let's say tomorrow a person decides to use an online wallet. The latter approach will violate your tests - they will need to be updated in order to click the online wallet now, even if the object's behavior is not violated.

Another example I saw is testing Repository.GetX (), where people enter the database to enter SQL records now in the unit test .. where it would be much cleaner and easier to call the .AddX repository ( x). Isolation is desirable, but not so much that it redefines pragmatism.

I hope I have not become too strong here. It just pains me to see that the object's APIs are “distorted for validation” to such an extent that they no longer look like the “simplest thing that can work.”

+1
source

I think you are trying to overestimate your definition of unit test a bit. You should test the public behavior of your class, not the tiniest implementation details.

From your code snippet, it looks like all you really need to care about is whether calling AddObjectToHistory IsAnyHistory true and b) RemoveLastObject ultimately causes IsAnyHistory to return false.

0
source

As pointed out in other answers, I think your options can be broken down like this.

  • You take a dogmatic approach to your testing methodology and add a constructor injection for the stack object so that you can insert your own fake stack object and test your methods.

  • You write a separate test for adding and removing, the remove test will use the add method, but will consider it as part of the test setup. As long as your test test passes, your removal should also be.

0
source

Source: https://habr.com/ru/post/948008/


All Articles