Suppose I have the following class

public class Residue { public int Id {get;set;} public int WarehouseId {get;set;} public int MaterialAssetId {get;set;} public virtual ICollection<ResidueHistory> Histories {get;set;} public Residue() { this.Histories = new List<ResidueHistory>(); } } 

I need to add a method that will recalculate the balances from the date passed as a parameter.

I add a new method to the class body.

 public void RecalculateResidueSince(DateTime since) { throw new NotImplementedException(); } 

The first test that I decided to implement is the way out of the method if the Histories property is empty;

Added to the solution a new project UnitTest:

 [TestClass] public class TestOfResidueMethods { [TestMethod] public void Test_RecalculateResidueForEmptyHistory() { var residue = new Residue(); residue.RecalculateResidueSince(DateTime.Now); } } 

and then I have a stupor and what to do next in the test method, if it was a function then I would check what happened as a result with what I expect.

Tell me how to test and whether to write such tests for such methods?

UPD:

After writing this method, I modified the test method as follows:

 public void RecalculateResidueSince(DateTime since) { if(!this.Histories.Any()) return; } 

those. if in fact there are no residuals then do nothing accordingly, but I won’t figure out how to check it in the test

  • 2
    If you have nothing returning functionality, then it either makes no changes at all, or it exists for side effects. Accordingly, you need to control the appearance of the expected side effects. - etki
  • @Etki updated the question - Bald
  • @Bald: The @Etki comment is still the answer to the question. You should not test the internal logic of the function, only its result. ` - VladD
  • @VladD in the current implementation, the method does not make changes, in the future I will be able to check the result based on the Histories collection, but at the current stage I have two possible options: if Histories does not contain data, then you just need to exit the method without doing anything otherwise throw an exception. I don’t need an exception here because there can be no residues - Bald
  • one
    @Bald: You must create a formal specification of the method (what should be the input, and what the output), and check it. Checking exactly how the desired effect is achieved is meaningless. If Histories is an internal implementation detail, then there is no need to check the effects on it. And if part of the official facade of the class, then the opposite is necessary. - VladD

2 answers 2

In tests, not return values ​​and not implementation of methods are tested, but a contract . A contract is some of the promises of a method about what it will do. Those. a peculiar set of "input-output" pairs. If the method then takes the parameters, the number of these promises (pairs) may increase and will depend on the combination of the parameter values.

In this case, the "output" may be different:

  • in the form of the return value of the function
  • as a change in the state of the current object or other objects, as well as calling methods in other objects (dependencies)

With the first type of contract everything is clear. Got a value, checked it out.

In the second type of contract, you need to check the changes in the state of the current object, and also, if you have dependencies and in tests you are mocking them, refer to these dependencies.


The contract of your method is as follows (from what you described in the question):

  • he recounts the balances, starting with the date transferred to him, while the earlier balances do not change
  • if there is no residue, he does nothing

Accordingly, your test cases should be as follows:

  1. Empty leftovers collection. Call the method and check that the state of the residuals has not changed. As a more rigid option: check that the state of the entire object has not changed / there have been no appeals to dependencies. (But this is already on the verge of and smacks of paranoia, although in some cases it may be justified.)
  2. Non-empty collection of balances for different dates. Call the method for a date that is greater than the maximum date in the residuals. Check that the state of the residual / entire object has not changed.
  3. Non-empty collection of residues. Call the method for a date that is more than the minimum, but less than the maximum date in the residuals. Check that residuals with a lower date have not changed and that residuals with a larger date have changed correctly.
  • those. in this case, after calling the method, it is enough for me to check that the Histories collection has not changed? - Bald
  • @Bald yes. As a tighter option, check that the state of the entire object has not changed and no dependencies have been caused (if you have them and you are mopping them). - andreycha
  • But in tests, not return values ​​and implementation of methods are tested, but a contract. A contract is some of the promises of a method about what it will do. it is not the same as checking the result of the method and thus checking the implementation of the method, but in other words. we also have the original data, we know what should happen in the end? - Bald
  • one
    @Bald, if you have input data 2 and 2 and the output should be 4, then it will not matter to you inside the 2 + 2 or 2 * 2 method - Grundy
  • 2
    after the @Grundy comment, I understood what it was about: under the verification of the implementation I meant that the output corresponds to what is expected, and not how it is done - Bald

Here you can check two things:

  1. the expected state (of its class or parameter (mocable), which is transmitted inside)
  2. exceptions thrown in (negative scenario only)
  • updated the question, added the initial implementation of the method. in this case, I don’t want to throw an exception, since the absence of residuals in this case is expected and I want to just quit the method, and if it’s not difficult, then how can you check the exception thrown by the method in the method - Bald
  • @Bald Read the dock for your test suite. This can be in the form of an attribute, like [ExpectedException (typeof (ArgumentException))] - free_ze
  • thank you so much for the hint that the returned exception can be determined by the attribute - Bald