Unit Testing specific classes

I have inherited a project that does not have interfaces or abstract classes, that is, concrete classes, and I want to introduce unit testing. Classes contain many functions that contain business logic and data logic; interrupting each SOLID rule ( http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29 ).

I had a thought. I was thinking of creating interfaces for each of the poorly designed classes, revealing all the functions. Then at least I can break up the classes.

I am relatively new to Unit Testing (I have experience working with a project that has been very well designed using interfaces in the right places). This is a good idea for this, i.e. Create interfaces for all concrete classes (exposing all functions and routines), only for unit testing?

I spent some time studying this, but I did not find the answer.

+4
source share
6 answers

Yes, this is a good start, however, the presence of interfaces is less priority than nested dependencies. If all your obsolete classes get interfaces, but are hidden inside, they are still interdependent, classes will still not be easier to test. For example, let's say you had two classes that looked like this:

Public Class LegacyDataAccess Public Function GetAllSales() As List(Of SaleDto) ' Do work with takes a long time to run against real DB End Function End Class Public Class LegacyBusiness Public Function GetTotalSales() As Integer Dim dataAccess As New LegacyDataAccess() Dim sales As List(Of SaleDto) = dataAccess.GetAllSales() ' Calculate total sales End Function End Class 

I know what you're already talking about ... "I would like the old code to be at least layered," but it can be used as an example of some outdated code that will be difficult to verify. The reason this is difficult to verify is because the code accesses the database and performs a laborious query on the database, and then calculates the results. So, to test it in its current state, you need to first write test data to the database, and then run the code to see if it will return the correct results based on this inserted data. The requirement to write such a test is problematic because:

  • It's a pain to write code to set up a test
  • The test will be fragile because it depends on the normal operation of the external database and contains all the correct supporting data.
  • The test will take too long.

As you rightly noted, interfaces are very important for unit testing. Therefore, as you recommend, add interfaces to check if this makes testing easier:

 Public Interface ILegacyDataAccess Function GetAllSales() As List(Of SaleDto) End Interface Public Interface ILegacyBusiness Function GetTotalSales() As Integer End Interface Public Class LegacyDataAccess Implements ILegacyDataAccess Public Function GetAllSales() As List(Of SaleDto) _ Implements ILegacyDataAccess.GetAllSales ' Do work with takes a long time to run against real DB End Function End Class Public Class LegacyBusiness Implements ILegacyBusiness Public Function GetTotalSales() As Integer _ Implements ILegacyBusiness.GetTotalSales Dim dataAccess As New LegacyDataAccess() Dim sales As List(Of SaleDto) = dataAccess.GetAllSales() ' Calculate total sales End Function End Class 

So now we have the interfaces, but really, how does this make testing easier? Now we can easily create a mock data access object that implements the same interface, but this is not the main problem. The problem is, how can we get a business object to use this mock data access object instead of the real one? To do this, you need to take your refactoring to the next level by introducing dependency injection. The real culprit is the key word New in the next row of business class:

 Dim dataAccess As New LegacyDataAccess() 

The business class clearly depends on the data access class, but it currently hides this fact. He lies about this addiction. He says come easy, just call this method and I will return the result - all that is required. When this is true, it requires much more. Now let's say we stopped him from lying about these dependencies and made him shamelessly expound them, for example:

 Public Class LegacyBusiness Implements ILegacyBusiness Public Sub New(dataAccess As ILegacyDataAccess) _dataAccess = dataAccess End Sub Private _dataAccess As ILegacyDataAccess Public Function GetTotalSales() As Integer _ Implements ILegacyBusiness.GetTotalSales Dim sales As List(Of SaleDto) = _dataAccess.GetAllSales() ' Calculate total sales End Function End Class 

Now, as you can see, this class is much easier to test. Not only can we easily create an object to access data, but now we can easily enter a data access object into a business object. Now we can create a layout that quickly and easily returns exactly the data that we want to return, and then see if the business class returns the correct calculation - there was no database.

Unfortunately, adding interfaces to existing classes is a breeze, refactoring them to use dependency injection usually requires much more work. Most likely, you will have to plan which classes are most suitable for the solution in the first place. You may need to create some old-school intermediate shells that work the way code is used, so you don't break existing code while you're in the process of refactoring the code. It is not fast and easy, but if you are patient and in it for long trips, you can do it and you will be glad that you did it.

+1
source

If your project does not have tests at all, before adding any unit tests I would rather create tests of a higher level (for example, acceptance, functional and / or integration tests).

When you have those tests, you know that the system behaves as it should, and also has a certain level of β€œexternal” quality (which means that the inputs and outputs of your program are expected).

Once your high-level tests work, you can try adding unit tests to existing classes.

I bet you will need to reorganize some of the existing classes if you want to be able to unit test so that you can use your high-level tests as a security network that will tell you that you broke something.

+5
source

This is a difficult task. I think you're on the right track. You will get ugly code (for example, creating header interfaces for each monolithic class), but this should just be an intermediate step.

I would suggest investing in a copy. Effectively work with legacy code . You can start by reading this distillation first .

In addition to the Karl options (which allow you to spoof through interception), you can also use Microsoft Fakes and Stubs . But these tools will not help you rework your code to adhere to SOLID principles.

+3
source

I would recommend an interface route to you, but if you want to pay for a solution, try one of them:

+1
source

Creating interfaces for testing classes is not a bad idea - the goal of unit testing is to implement it if the functions in the class function properly. Depending on the classes you work with, this may be easier said than done - if there are many dependencies on global states, etc., you will need to mock it.

Considering how valuable single tests are, to put a little effort on them (to the limit), the developers you work with will come in handy.

0
source

I prefer to create interfaces and classes, since you need to check things, not all in advance.

Besides interfaces, you can use some methods for testing outdated code. I often use "Extract And Override", where you extract part from the "unstable" code inside another method and make it redefinable. Get the class you want to test and override the "untestable" method with some code.

Using a mock structure will be as simple as adding the Overridable keyword to a method and setting the return using the framework.

In the book you will find many methods " " Work efficiently with obsolete code . "

One thing about existing code is that it is sometimes better to write integration tests than unit tests. And after you do the testing, you will create unit tests.

Another tip is to start with modules / classes that have fewer dependencies, so you become familiar with the code with less pain.

Let me know if you need an example of "fetch and override";)

0
source

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


All Articles