Should I use IEquatable to simplify plant testing?

I often work with classes representing objects created from factory. For easy testing of my plants, I usually implement IEquatable<T> , as well as overriding GetHashCode and Equals (as suggested by MSDN ).

For instance; take the following entity class, which is simplified, for example, for purposes. Usually my classes have even more properties. Sometimes there is also a collection that in the Equals method I check with SequenceEqual .

 public class Product : IEquatable<Product> { public string Name { get; private set; } public Product(string name) { Name = name; } public override bool Equals(object obj) { if (obj == null) { return false; } Product product = obj as Product; if (product == null) { return false; } else { return Equals(product); } } public bool Equals(Product other) { return Name == other.Name; } public override int GetHashCode() { return Name.GetHashCode(); } } 

This means that then I can perform simple unit tests (assuming the constructor is checked elsewhere).

 [TestMethod] public void TestFactory() { Product expected = new Product("James"); Product actual = ProductFactory.BuildJames(); Assert.AreEqual(expected, actual); } 

However, this raises a number of questions.

  • GetHashCode is not actually used, but I spent time implementing it.
  • I rarely really want to use Equals in my actual application outside of testing.
  • I spend more time creating additional tests to ensure that Equals working properly.
  • Now I have three more methods to maintain, for example. add class property, update methods.

But this gives me a very neat TestMethod .

Is this a suitable use of IEquatable , or should I use a different approach?

+5
source share
2 answers

Whether this is a good idea or not depending on what type your factory creates. There are two types of types:

  • Types with semantics values ​​(value types for short) and

  • Types with referential semantics (referential types for short ones.)

C # usually uses struct for value types and class for reference types, but you don't need this, you can use class for both. The fact is that:

  • Value types are intended for small, usually immutable, autonomous objects whose main purpose is to contain a specific value, and

  • Link types are objects that have a complex mutable state, possibly links to other objects and non-trivial functions, that is, algorithms, business logic, etc.

If your factory creates a value type, then be sure to go ahead and make it IEquatable and use this neat trick. But in most cases, we do not use factories for value types, which are usually quite trivial, we use factories for reference types, which tend to be quite complex, so if your factory creates a reference type, then really, these object types are intended just for comparison by reference, so adding the Equals() and GetHashCode() methods is by no means wrong.

Take a hint of what happens with hash cards: having Equals() and GetHashCode() in a type usually means that you can use an instance of that type as a key in the hash map; but if the object is not an immutable value type, then its state may change after it is placed on the map, in which case the GetHashCode() method will begin to evaluate something else, but the hash map will never worry about calling GetHashCode() again, to move the object to the map. The result in such cases tends to be chaos.

So the bottom line is that if your factory creates complex objects, then you might need to take a different approach. The obvious solution is to call factory and then check each property of the returned object to make sure they are all as expected.

I could suggest improving this, but beware that I just thought about it, I never tried it, so it may and may not be a good idea in practice. Here he is:

Your factory supposedly creates objects that implement a specific interface. (Otherwise, what is the point of having a factory, right?) So, you could theoretically envisage that newly created instances of objects that implement this interface should have certain properties initialized with a specific set of values. This would be a rule imposed by the interface, so you could associate some function with an interface that checks if this is true, and this function can even be parameterized with some hint of expecting different initial values ​​under different circumstances.

(The last thing I checked in C #, an interface-bound method was usually an extension method; I don’t remember from what point of view, C # allows static methods to be part of the interface, or C # designers have added something like this to the language as neat and elegant as the standard Java interface methods.)

Thus, using the extension method, it might look something like this:

 public boolean IsProperlyInitializedInstance( this IProduct self, String hint ) { if( self.Name != hint ) return false; //more checks here return true; } IProduct product = productFactory.BuildJames(); Assert.IsTrue( product.IsProperlyInitializedInstance( hint:"James" ) ); 
+4
source

For test code, you can use reflection-based equality, for example: Comparing object properties in C #

Many testing libraries provide such a utility, so you do not need to change the design of your production code in accordance with the tests.

+1
source

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


All Articles