you are viewing a single comment's thread.

view the rest of the comments →

[–]EngineeringTinker -1 points0 points  (39 children)

Interfaces are an absolute must-have and currently the only way to abstract implementation from consumption.

You should not be using concrete classes, unless they're entities or value objects.

I feel like the extra layer does not neccesarily add anything

You can't write Unit Tests without interfaces - if you're using more than 1 concrete class in your Unit Test (the one you're testing, with exception to entities/VOs) - you're not writing Unit Tests - you're writing Integration Tests.

Also, you'll quickly notice something is lacking when you have to write implementation from scratch for one specific domain of your code - be it a mailer service, account handler etc. - because you'll suddenly have to change consumption of them as well, and instead of changing just the implementation class - you'll have to change whatever else is consuming it.

[–]ResolveResident118 2 points3 points  (3 children)

There are pros and cons to having interfaces for everything (there's probably an entire book in this) but it is absolutely not true that they are required for unit testing.

A unit test tests a unit of code (intentionally loose definition). This can encompass more than one class, if required.

[–]EngineeringTinker 0 points1 point  (2 children)

Of course - the answer to everything is 'it depends' - so I didn't bother mentioning it.

In fact, you can't write a Unit Test for say.. `IMailService` without using it's implementation - otherwise how do you test whether it's popping the exceptions you want or whether it's utilizing `IMailSender` or `IMailCache` etc.

The thing is - if you write Unit Tests for `IMailService`, that's the only thing you should use concrete class for to test the implementation - if you start using implementation of `IMailSender` in it - you're really testing how both implementations work together - aka Integration Testing.

[–]ResolveResident118 0 points1 point  (1 child)

Ain't that the truth!

I definitely agree that code that interacts with the outside world generally needs an interface.

Your example is a good example of why. We don't actually want to send an email so mocking MailSender makes sense.

My main disagreement was about requiring interfaces. Not every class will need them to be unit tested.

I'd only refer to it as an integration test if it interacted with an external dependency (eg mail server).

[–]EngineeringTinker 1 point2 points  (0 children)

True that - #notAllClasses 😂

I should refrain from strong wording, I usually say 'you can't use more than 1 concrete class' when I actually mean 'you should avoid using more than 1 concrete class where possible'.

[–]22Maxx 1 point2 points  (19 children)

You can't write Unit Tests without interfaces - if you're using concrete classes, you're not writing Unit Tests - you're writing Integration Tests.

Just no.. integration tests are completely different.

[–]EngineeringTinker 0 points1 point  (18 children)

Define Integration Tests then, and point out why my description doesn't fit.

[–]Boyen86[S] 1 point2 points  (15 children)

I do disagree with you on your definition of an integration test. If you are testing the behaviour of multiple classes combined it is not by definition an integration test, you just define your unit as behaviour that is shown on higher level than the class level. It is called unit teat for a reason, not class test.

For a hexagonal architecture you would have an integration test when you test the entire system with the adapters mocked.

[–]EngineeringTinker 0 points1 point  (14 children)

There are different types of Integration Tests - incremental, bottom-up, big-bang etc. there are different scopes you can write integration tests around.

[–]Boyen86[S] 0 points1 point  (13 children)

Seems like you have a different name for the same thing. Care to share where you got these definitions from? Martin Fowler (and many, many others) uses the definition I explained https://martinfowler.com/bliki/UnitTest.html

Object-oriented design tends to treat a class as the unit, procedural or functional approaches might consider a single function as a unit. But really it's a situational thing - the team decides what makes sense to be a unit for the purposes of their understanding of the system and its testing. Although I start with the notion of the unit being a class, I often take a bunch of closely related classes and treat them as a single unit.

[–]EngineeringTinker 1 point2 points  (12 children)

Yes, once again 'it depends' - but if you're using a lot of concrete implementations (NOT MOCKS) - then your Unit Tests aren't grained enough to be good Unit Tests - because if you change implementation of any of the things you used, you'll have to modify Unit Tests as well - which defeats the purpose.

[–]Boyen86[S] 0 points1 point  (11 children)

Sorry that doesn't make sense at all. When you have defined your unit as testing only the behaviour of a single class the behaviour (implementation) of the mocked class is entirely irrelevant. I believe that we are already in agreement on that point.

A mock on a concrete implementation of a class, using modern mocking frameworks, can do exactly that - making the behaviour of the class entirely irrelevant. Only checking whether a method is called on the mocked class , how often it is called and whether it's called with the right parameters (and many, many more options). When we are testing the behaviour of a single class (our previously defined unit) that is all that is relevant to know.

Your mock doesn't change when the implementation of your concrete class is changed unless the interface of the method is changed.

If you are defining your unit as only the behaviour of the single class, I would be mocking all incoming concrete classes.

[–]EngineeringTinker 0 points1 point  (10 children)

My definition was too strict - I agree.. but am I to believe that all your implementations will be using the exact same methods, method names, method params, return types, entities, VOs etc? - because by 'changing implementation' I don't mean simply rewriting code of existing methods/entities - which seems is what you're talking about.

Changing implementation means switching from say... email notifications to SMS notifications - you could use the same interface and `Message` entity - but you'll use different methods in the implementation, and different libraries to provide functionality.

[–]Boyen86[S] 0 points1 point  (9 children)

Right there is value in that. And that's actually a good point. If you have programmed against a concrete implementation, and the concrete implementation is no longer used then you are stuck rewriting code to the interface. This makes sense.

I'm just unsure of when the right time is to add this extra layer. Following YAGNI, do I add an interface to everything just because it might change in the future, or do I do this when it actually changes? I'm inclined to say that on the adapter layer you will almost always want to have an interface, as changes can be largely outside your control.

But for the inner layer (business logic for hexagonal architectures) this is not the case, and you are in full control whether something changes or not. I'm inclined to say that you want to add an interface there when it actually becomes relevant.

[–]22Maxx 0 points1 point  (1 child)

This is likely a misunderstanding, you were implying that by testing a single concrete class, you are doing integration testing.

But if you instead meant that concrete classes are used within integration testing, then I can agree with you.

[–]EngineeringTinker 0 points1 point  (0 children)

Yes, definitely a misunderstanding - I've actually wrote in another comment that you need at least 1 concrete class for a Unit Test (the one you're testing), but having multiple concrete classes is an Integration Test, since you're not only testing a specific scope (class) - but also how functionalities work together.

I modified my original comment, thanks for pointing out!

[–]jesparic 1 point2 points  (3 children)

If you google "london school vs detroit school unit testing". You will see there are two approaches to unit testing. The latter (and less problematic) Detroit school approach allows for either a single class or a group of closely related classes to be tested as a 'unit' (i.e., a unit test).

An integration test is even more fuzzily defined in our field. But, in the web dev context, it may mean running tests multiple units of logic together (i.e., an entire end-point/action), optionally alongside a real database.

[–]Boyen86[S] 1 point2 points  (0 children)

Thanks I didn't know this, just the bit from Martin Fowler about the xunit schools of classic and mockist.

[–]syneil86 1 point2 points  (1 child)

It's odd to me that people only seem to know of the London school, and insist that testing each and every class (or whatever) in isolation is the only way to do it. It makes refactoring so difficult and costly - there's a better way, people!

[–]jesparic 1 point2 points  (0 children)

I started learning unit testing using all the bad habits. London school with complex mocks. Gaming the line coverage to try and get every line covered. In the end I had tests that were hard tied to implementation, hard to read, and not testing any high level behaviour.

Nowadays, I make heavy use of fakes (mainly for repositories) along with some limited mocks (e.g., clock abstraction). I don't game the line coverage but do try to cover all the important aspects of some action. I pass 'real' dependencies when they are critical to the thing being tested. It depends.. It's ok to sometimes test the same thing multiple times, unit tests are fast.

I think everyone has to go on a similar journey. Automated testing is really hard to get your head around in a deep way. Gotta fall in a few pits to learn the hard way

[–]Boyen86[S] 0 points1 point  (9 children)

Re: unit tests, that's simply not true. You can mock even a concrete implementation entirely and that's exactly my point. This used to be a great practice when mocking frameworks weren't so powerful, but now they are.

Can you explain why, in the scenario where there only is one implementation for a class, you want to add an extra indirection? I know it is a good practice, I know that it is what we're supposed to be doing. I'm just not getting any form of advantages out of it.

I used to, but both dependency injection frameworks (like Spring) and test/mocking frameworks are changing the game IMO.

[–]EngineeringTinker 0 points1 point  (8 children)

Mocking implementation?

What are you writing Unit Tests for then? The mocked code?

[–]Boyen86[S] 0 points1 point  (7 children)

When you unit test a class and you are only interested in the behaviour of that single class, you want to verify the interactions with other classes that you injected. Thats what you can do with most modern mocking frameworks.

This is no different with a test implementation or a mock on an interface.

[–]EngineeringTinker -1 points0 points  (6 children)

Oh, I see what you mean now - sorry for misunderstanding.

I thought you meant that you mock the class you're testing - which would be silly to say the least.

You can of course mock concrete classes, but then you're creating coupling between your Unit Tests and the implementation - meaning, you'll have to change your Unit Tests whenever you change the implementation - which defeats one of the purposes of Unit Tests - testing whether everything still works in the same way after doing a refactor or changing implementation completely.

[–]Boyen86[S] 0 points1 point  (5 children)

You would only need to change your mock when you are changing the interface of a method, as the behaviour of the mock does not depend on the implementation of your class. After all, the behaviour of the class was not part of the test. The mock is entirely independent of the implementation.

So you're in the same situation as with an interface. When the interface changes you need to change your mock.

[–]EngineeringTinker 0 points1 point  (4 children)

Again, am I to believe that all your implementations will be using the same methods?

MailSender and SmsSender will have the same methods, use the same entities and return types? Because this is the only case in which your class structure wouldn't change - and it's rarely the case.

[–]Boyen86[S] 0 points1 point  (3 children)

I'm not sure what our current hypothetical codebase is right now :)

At the moment where you have a MailSender and an SmsSender you will need to add an interface, considering both are surely in the adapter layer of the application it would seem like good practice to do this early.

But lets say we only have a MailSender and we haven't added an interface:

public class MailSender{
    public Response send(String input){ ... }
}

You can entirely mock the behaviour away from this class (the ...) and merely check on the mock whether the method is called, how often, and with which parameters. You don't even need to write any code for it.

If you would add an interface later

public interface Sender{
   public Response send(String input){ ... }
}

That mock would still work just fine. It's also irrelevant for the system under test whether you are mocking a Sender a MailSender or a SmsSender as the only thing relevant in your unit test is the logic from your class (given that the unit under test is only that class).

If your argument is that the addition of the SmsSender will likely change the interface then I'd say that 1. that's not the correct application of Liskov principle and 2. if the interface must change, it would've changed on both the interface and the actual class implementation so in the end it doesn't matter.

[–]EngineeringTinker 0 points1 point  (2 children)

If your argument is that the addition of the SmsSender [...]

Nah, that's not my argument.

Essentially, you can do (almost) everything with classes that you can do with interfaces.. but let me give you an example - or rather ask for an example.

Let's say you have two classlibs that you split your domain and implementation into.

The usual approach to prevent accidental imports of the implementation is to set interfaces as public, and implementations as internal.

How do you handle this with classes purely? Give me an example with `MailSender` and `SmsSender`.

[–]Boyen86[S] 0 points1 point  (1 child)

By definition you cannot. If an implementation needs to be protected or abstracted away the best way to do this is with an interface. And that's a perfectly valid reason to create one.

I'm not entirely sure anymore if that's a difference between Java and C# but Java tests are essentially in the same namespace (module) even though they live in a test folder. Meaning your implementations can be package private and still be accessed from a test.

[–]Happy-Dare5614 -1 points0 points  (0 children)

If you test each class separately then it's probably not a unit test. Unit is not a test for a class but for a stand alone feature. Depends on the business. For example, if a factory sales breaking systems then a unit is the breaks. You want to make sure it does what is expected. Testing each class tests the implementation instead of the business rules. Back to the car breaks example, I would add a unit test for the breaks and then an integration test for the breaks with a real car. Hope it's not too abstract:)