1

Our project have a lot of layers,

  • Entities

  • Repositories

  • UnitOfWorks

  • Domain Services (Business Logic)

  • Validation

  • Infrastructure

  • Application Services

    etc..

We are only writing unit tests for Business Logic layer and mocking other parts. Is it possible and reasonable to do TDD in this case? (All Business Logic classes consuming other classes via DI with constructor injected dependencies)

Joe Gage
  • 47
  • 1
  • 3
  • 3
    Why not? What did you try? – Doc Brown Jul 04 '18 at 19:50
  • 2
    It is possible and you can reason about that as core logic is isolated from other parts of application. But there are other approaches can be applied, for example mock only external resources and use actual implementation for when testing feature as unit. If you want ask for other approaches then make it clear in the question and provide more specific context details based on which we can help you. – Fabio Jul 04 '18 at 21:59
  • -1, and voting to close as unclear (it is quite unclear if you mean "TDD just for the BL" or "TDD for the other parts as well"). Moreover, you did not answer my questions above. . – Doc Brown Jul 05 '18 at 11:26

3 Answers3

2

We are only writing unit tests for Business Logic layer and mocking other parts. Is it possible and reasonable to do TDD in this case?

Yes, in fact that is highly recommended over the "unit test everything" attitude.

Remember, business logic is the interesting stuff that makes decisions. Everything else is just boring infrastructure code that wires up connections.

The boring infrastructure is easy to read but hard to test. The point of tests is to make code easy for humans to read. Boring easy to read code doesn't need unit tests.

This is why we don't unit tests GUI's. Instead we move all interesting logic out of the GUI and put it someplace testable.

The name for seperating hard to test code from interesting code is The Humble Object pattern1,2

TDD works best when you follow this pattern. It basically says if your code is interesting and hard to test then separate the interesting stuff from the hard to test stuff. That can be as simple as turning one function into two. One you unit test. One you don't.

Thinking you have to unit test everything in TDD is quick way to convince people that TDD doesn't work. No that's not what it's for.

Keep in mind there are many other kinds of tests besides unit tests. The slow ones are sometimes called integration tests. That's when you test what happens when you connect the things that TDD isolates. Those tests mostly just show that the connections work. These tests are slow. You don't run them often. Just be sure you run them at least once before you publish.

TDD is about the really fast tests. So fast you run them every time you compile. Some people run them on every keystroke.3 So please, don't confuse these two different kinds of tests and blame the resulting nightmare on TDD. That's not its fault.

Use TDD on what it's for: the interesting stuff.

candied_orange
  • 102,279
  • 24
  • 197
  • 315
  • I almost agree. But when you write about separating code to make it testable - this is not TDD! TDD is about writing test code _before_ program code to avoid such situations with bad/no separation (although it's not the only reason to do TDD). – Andy Jul 05 '18 at 12:15
  • @Andy separating code doesn't necessarily mean it ever existed before the separation. But even if it does exist already that doesn't mean you can't refactor to testable code. It's just a huge pain, less safe, and has a much smaller payoff. Consider reading [Working Effectively with Legacy Code](https://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052). – candied_orange Jul 05 '18 at 15:19
  • tbh I think the whole point of TDD is to stop you having to make value judgements about whats worth testing. Test everything! GUI, database, BL. dont write code untill you have a failing test – Ewan Jul 05 '18 at 15:23
  • @Ewan I make that value judgement all the time. It's mostly based on speed. Slow tests need their own pile. I want fast tests I can run extremely frequently in their own pile. Slow tests get run before I show my code to others. – candied_orange Jul 05 '18 at 15:25
  • Sure, the only question is whether its "TDD" if you skip writing tests for some of your code – Ewan Jul 05 '18 at 15:28
  • 1
    @Ewan the only question is whether it is "fit for purpose". I'm not a religious zealot that will faithfully apply TDD to everything under the sun. I am a hard core code monkey that will gleefully spend hours practicing TDD at home finding every little way it can be applied effectively. I will then casually show it off at work to a few fellow coders and see who can use it without it going horribly wrong by being misapplied and misunderstood. I will use it only where I can ensure it's success. I will not use it as a silver bullet. I'm not hunting werewolves. I have real problems to solve. – candied_orange Jul 05 '18 at 15:41
  • @Ewan I've contrasted faithful and pragmatic TDD [here](https://softwareengineering.stackexchange.com/a/322260/131624). – candied_orange Jul 05 '18 at 15:41
  • I don't think anyone would dispute the wisdom of a pragmatic approach. But its not TDD. – Ewan Jul 05 '18 at 15:47
  • @Ewan I've cited my sources. How about we see yours? – candied_orange Jul 05 '18 at 20:37
  • page x rule 1 https://books.google.im/books?id=CUlsAQAAQBAJ&printsec=frontcover&source=gbs_ge_summary_r&cad=0#v=onepage&q&f=false – Ewan Jul 05 '18 at 20:48
  • page 'x' roman numeral is in the preface. I'm sure we don't disagree on the three rules of tdd. – Ewan Jul 05 '18 at 21:13
  • @Ewan yes yes first write a failing test. Show me where Beck teaches us how to do that with a GUI. – candied_orange Jul 05 '18 at 21:14
  • are you saying TDD is unsuitable for GUI, or are you saying not doing TDD on the GUI _is_ doing TDD? – Ewan Jul 05 '18 at 21:19
  • also page 10 "You could have only application-level tests and be doing TDD. " – Ewan Jul 05 '18 at 21:42
  • TDD does not "isolate" anything, nor is it about "really fast tests". Of course, we should strive for tests that are not too slow, nor too fragile. But a common mistake I see people making is to focus on writing tests that verify the behavior of units of code, rather than verifying *requirements*. All tests that use a mocking API, usually, are bad tests. – Rogério Aug 20 '19 at 20:47
  • @Rogério TDD encourages isolation because it forces you to flex your code early. Requirements and behavior should add up to the same thing in the end or you're not meeting requirements. Tests should show if they are. Requirements and tests should both not care about HOW. Changing how without breaking anything is what refactoring is for. – candied_orange Aug 20 '19 at 21:18
  • I agree with everything you said in your last comment. My only point of contention is with the use of the word "isolation", which is often used in the context of mocking dependencies. So, while TDD may be about the logical isolation between units, it's not about isolation through mocking of dependencies, which is (in general) a bad practice IMO. – Rogério Aug 20 '19 at 21:51
  • @Rogério Mocking means a LOT of things. Fake stub dummy spy. If you want to convince me that ALL those ideas are bad in every situation you need to make a better case. If that's not what you mean then please be clear. I can't see how to improve this answer based on what you've said so far. – candied_orange Aug 20 '19 at 22:01
  • Ok. What I don't like in this answer is that it says "yes" to "writing unit tests for Business Logic layer and mocking other parts". And this, in my experience, only leads to bad tests. I think it's a bad idea to mock Repositories, for example. Also, in this case, the OP is not testing its "Application Services", even though he probably could do it without much trouble. – Rogério Aug 21 '19 at 16:22
  • @Rogério I'm not sure I understand your objection. Are you saying every test should exercise the whole system from end to end? If not how do you recommend isolating? – candied_orange Aug 21 '19 at 23:40
  • I am saying that proper TDD-based tests should be driven by meaningful business-level requirements, and as such they would tend to be as realistic as possible. Due to pragmatic reasons, we may not want to write end-to-end UI-level tests. In my case, I write subcutaneous integration tests, which exercise all code involved in individual business scenarios, except for UI code. Isolation through mocking is something I do as well, but only in exceptional circunstances. – Rogério Aug 22 '19 at 22:28
  • @Rogério we might be disconnecting on a vocabulary level. Exercising all code involved doesn't make it an integration test. So long as it's an isolated unit it's a unit test. Doesn't matter how many classes live in it. It's when something like the database has to be up and working to run the tests that they are integration tests. Are we using these terms the same way? – candied_orange Aug 23 '19 at 01:08
  • I guess so. In my case, the tests I write are [subcutaneous integration tests](https://martinfowler.com/bliki/SubcutaneousTest.html), and the database sure has to be up and working (which is not a problem, it always is anyway - why wouldn't it be?). – Rogério Aug 23 '19 at 02:35
  • I prefer that my [unit tests](https://www.artima.com/weblogs/viewpost.jsp?thread=126923) not wait for the database, care if it's up, or need it to be in some special state to work. I'll involve the database when that's what I'm testing. – candied_orange Aug 23 '19 at 02:41
  • @Rogério so just as you would need to keep behavior code out of the UI, I need to keep interesting behavior out of the database. So I don't put business rules in saved procedures. This way when I do an integration test all I'm really testing is that someone hasn't kicked a cord loose. – candied_orange Aug 23 '19 at 20:39
  • I don't develop with stored procedures since the 90's. There is no behavior *in* the database. But there is business logic in database *queries*, and this is inevitable. Also, I said I do *integration* tests, not *unit* tests. BTW, what Michael Feathers says about unit tests is not in line with what Kent Beck or [Martin Fowler](https://martinfowler.com/bliki/UnitTest.html) say, IMO. My integration tests don't have the problems he mentions. – Rogério Aug 28 '19 at 16:00
  • Personally, I don't think what you do is really TDD, because you apparently don't write your tests from the requirements dictated by real business scenarios. In reality, the vast majority of use case scenarios in a real-world business application involve access to persistent data, ie, they need to touch the database. Therefore, any real test, driven from requirements as TDD implies, would involve database access. Writing tests for *units of code* is a common mistake. Good tests are written for meaningful "units" of application functionality (aka "features"). – Rogério Aug 28 '19 at 16:25
0

Strictly speaking no.

TDD requires that everything you write has a test.

However, we all know that some tests are more useful that others. If you have complicated business rules and tests that validate your business rules match the spec then those are pretty useful tests.

If I come along as a new dev and break the database reading code then I probably can see its broken and know how to fix it. It would be nice to have a test but not essential.

If I break an obscure business rule edge case that was designed months or years ago, then I might not even notice anything is wrong without those unit tests.

Ewan
  • 70,664
  • 5
  • 76
  • 161
  • TDD requires to *start with a test and keep the test code / program code cycle*. It does not require a test for every piece of code you write (which rather indicates for coverage, what was not the original intention). – Andy Jul 05 '18 at 12:38
  • If you always start with a test, all your code will have tests. Maybe you dont cover all the paths, but you dont skip whole layers – Ewan Jul 05 '18 at 15:19
  • Did you notice OP did not say "is it reasonable to do TDD for all layers" (nor "is it reasonable to do TDD for the BL", they actually refused to add any clarification what they meant). If I got you right, you took the first interpretation of the question, is that correct? – Doc Brown Jul 05 '18 at 19:24
  • my interpretation is "if we only write tests for the BL: can anything we do test wise (is it possible) be correctly called TDD" I have a feeling the OP means something other than TDD when they say "TDD" perhaps they mean, "is DIing mocks not proper testing?" or "Is only doing tests for the BL a valid approach?" but those are kind of vague wishy washy questions with only feel good "whatever you do is fine!" style answers. I prefer the more brutal, direct, more accurate, no nonsense, less popular answer. – Ewan Jul 05 '18 at 20:05
  • TDD is a programming technique. Can it be applied to individual parts of a system like the business layer, whilst other parts are developed differently? IMHO yes, absolutely. So what is your issue with this? – Doc Brown Jul 06 '18 at 14:56
  • If you don't even attempt to follow TDD for 6 out of 7 parts of your application then you are not doing TDD. You _could_ maybe say 'we are using TDD for the BL layer' or 'we are applying aspects of TDD' but its not 'possible and reasonable' to say that skipping tests for most of the app is 'a case of doing TDD' – Ewan Jul 06 '18 at 15:14
  • besides, is the only part of my answer you disagree with the first 3 words? – Ewan Jul 06 '18 at 15:15
  • Strictly speaking I guess that would mean you disagree with the whole thing ;) – Ewan Jul 06 '18 at 15:16
  • @Ewan: I did not say so far I disagree with your answer, since I actually wondered if you really meant to take such a strange, black-and-white, holy-war position - I was under the impression I missed something. – Doc Brown Jul 06 '18 at 22:04
  • When the question references a particular methodology I think you can give a simple black-and-white answer. But I don't think im being particularly holy war about it. I'm not saying TDD is good or bad and the bulk of my answer recommends a pragmatic approach. If anything I find the equivocating about what is true-TDD/Scrum/OOP or whatever more dogmatic as it precludes any discussion of the downsides of the practice in question and can obscure the advantages of 'following the rules untill you understand the why'. – Ewan Jul 06 '18 at 22:27
  • You're right, Ewan. Strictly speaking, TDD *does* require thal all code be covered by tests. That is easier said than done for UI code (specially HTML/JS code), though. A middle-of-the-road approach with "subcutaneous tests" (Martin Fowler's terminology) is often more pragmatic, while still allowing for most of an application's code to be developed through TDD. It's the approach I normally use (that said, having an additional but smaller test suite with UI-driven tests is a good idea as well). – Rogério Aug 20 '19 at 20:41
0

Indeed, it's a very valid approach, as business complexity should be laying on the domain layer. Here, IMO TDD shines here as you don't have to do a lot of ceremony as you typically work with simple types - domain models , value objects, etc - and you can center yourself in modelling policies and behaviors.

I am personally not a big advocate of TDD'ing from the service layer. Here we should be having mostly orchestration code that calls to the domain and I/O adapters. The degree and quality of the patterns and abstractions used here might vary wildly between developers, and TDD'ing here means a certain degree of coupling to the implementation - you assume a sets of abstractions to be injected and knowledge of the services - which ossifies the code being tested and discourages its refactor further down the road. On the other hand, I can see TDD working from the controller, so you are only coupled to your application's external world contracts.

It's a very valid option though to do TDD from the service layer or controller itself on CRUDish apps, setting up a real database. Sure, test executions will be much slower but on this breed of apps you're typically writing a database wrapper so you cannot just assume certain behaviors of such a critical piece of your system.

George
  • 168
  • 4