Unit testing: how to test methods with a lot of un

2019-07-04 07:59发布

问题:

I am really new to unit testing, and I can't figure out the proper way to do it for my case, though I spent crazy amount of time on research.

My code base is huge (~3 years of work), very coupled unfortunately, hard to test and no unit testing has ever been done on it.

So for instance, when trying to test a collection class ProductCollection, more specifically, the bool MoveElementAtIndex(Product productToMove, int newIndex) of it, I am encountering the following problems:

  • first I have to initialize this new ProductCollection()
  • the constructor initializes another hand made class: new KeyedList<ID, Product>. I suppose this should not be called within this constructor, since I'm not testing KeyedList.
  • next, I am trying to add 3 products to this ProductCollection.
  • I am then first creating these 3 new Product().
  • But the constructor of Product class does several things
  • It computes a unique ID for the newly created product: this.ID = IDUtils.ComputeNewIDBasedOnTheMoonPhase(). I suppose I should not test this neither, since it's not my scope. How should I avoid such calls at this level of deepness?
  • The same Product constructor assigns some default properties for this product: this.Properties = new ProductProperties(folderPathToDefaultProperties). This should not be called from my simple FieldCollection.MoveElementAtIndex test, right?
  • Let's say I finally have my product objects now, and I'm trying to add them to my collection.
  • But the ProductCollection.Add(MyProduct) checks if the underlying KeyedList already contains the product. This is also business logic that I should avoid, not being related to my test. The question is how?
  • Also, in this Add method, some events are raised, notifying the system about several things (e.g., that a new product was added to the collection). These should also not be fired at all I suppose.
  • And then finally, when I have my products added, I'm calling the desired SUT: the move elements method.
  • But this method also has logic that can be out of scope for my test: it verifies that the underlying KeyedList actually contains those fields, it calls KeyedList.Remove(), KeyedList.Insert() for its moving logic, and it fires events like CollectionModified.

If you could just explain me how to do this unit test properly, how to avoid underlying objects from being called, I would really appreciate it.

I am thinking of Microsoft's Moles framework (VS2010), since I have the impression that it does not require me to refactor everything, as this is absolutely not an option. But having tried it already, still can't find a proper way to use it.

Also, I have the impression that this concrete example will help many in my situation, because this is how code in real world usually is.

Any thoughts?

回答1:

Your code isn't designed with unit testing in mind which makes it very hard to do so. I'd advise you to design and unit test your new code properly and try to refactor the most important things so you can unit test that.

Examples:

But the constructor of Product class does several things. It computes a unique ID for the newly created product: this.ID = IDUtils.ComputeNewIDBasedOnTheMoonPhase(). I suppose I should not test this neither, since it's not my scope. How should I avoid such calls at this level of deepness?

To fix this you should pass an interface IUtils to your Product constructor. To test your Product class, you can create a mock of your IUtils that returns a set value. You can do the same with your ProductProperties.

Also, in this Add method, some events are raised, notifying the system about several things (e.g., that a new product was added to the collection). These should also not be fired at all I suppose.

This comes down to design. You could use an observer pattern and not have any observers while unit testing.



回答2:

I would recommend to use ApprovalTest. This is great tool to start testing legacy systems with not the best design.

Don't bother now between difference of unit and integration tests and don't bother with isolating your class completely. Your code is probably not best suited for testability and when you start to isolate everything from one another - you will end up of huge arrange sections and with very fragile tests.

On the other hand, there are external resources (web services, databases, file systems etc), that you have to isolate. Also all non-deterministic behavior should be isolated (Random, current time, user input and so on)

I just recommend to create a safety net of verification tests, that will help you to change software in the direction of testability and will tell you if you made a breaking change in your code.

Read Michael Feathers Working Effectively with Legacy Code



回答3:

This is a common problem and the reason why we have frameworks to fake objects for us. Stubs or mocks allow us to create a test harness around a class without having to instantiate lots of other classes and services. There are a number of suchh frameworks. This is a very useful blog of three common frameworks. http://www.richard-banks.org/2010/07/mocking-comparison-part-1-basics.html.

I am pretty new to unit testing myself and found the book The Art of Unit Testing by Roy Osherove very helpful. Roy has a blog and these are some of his posts on Unit Testing http://osherove.com/display/Search?moduleId=10002929&searchQuery=Unit+Testing

The other thing that needs to be thought about is how closely coupled your classes are. It may not be that easy to do it but you might want to look at Dependency Injection. This allows classes to be more loosely coupled and therefore easier to test. I found the book Dependency Injection in .NET by Mark Seemann to be a good introduction.

After my reseach I settled on NUnit as the Test Framework, NSubstitute for mocks and stubs, Fluent Assertions to make writing tests easier and NInject for Dependency Injection



回答4:

you should go with either microsoft moles framework or microsoft fakes frameworks. These frameworks enables you to change the behaviour of a function.

In a proper unit test, all external method calls shall be mocked to isolate the code to be tested. Moles / Fakes is really useful in creating mock objects / stubs etc.

UPDATE: Theoratically, it is best to mock all external method calls to isolate the code. And even in some practical cases, you have to mock all external methods:

void UpdateSomeX(X x)
{
   this.validator.Validate(x);
   x.UpdateDate = DateTime.Now;
   this.context.Attach(x);
   this.unitOfWork.Save();
}

Guess what; you will endup with mocking all external method calls. Then do you need to test this method? The answer is yes, but the details of "yes" is out of scope of this question.