TDD: why might it be wrong to let app code know it is being tested, not run?

后端 未结 5 464
说谎
说谎 2021-01-19 19:35

In this thread, Brian (the only answerer) says \"Your code should be written in such a fashion that it is testing-agnostic\"

The single comment says \"Your code shou

5条回答
  •  野趣味
    野趣味 (楼主)
    2021-01-19 19:48

    TDD: why might it be wrong to let app code know it is being tested, not run?

    1) Carl Manaster has brought a excellent and short answer. If your implementation has a different behavior according to that is tested or not, your test has no value as it doesn't reflect the real behavior of the application in production and therefore it doesn't validate the requirements.

    2) Test-Driven Development has no relation with the fact to let app code know it is being tested. Whatever the development methodology you use, you may introduce this type of error.

    With my TDD experience, I think that TDD prevents from letting the app code know it is being tested since as you write the unit test in first intention and you do it suitably you have the guarantee to have a naturally testable applicative code that validates the app requirements and that has no knowledge of the tested code.

    I imagine rather that this kind of error could happen more probably when you create the test code after writing the applicative code as you may be tempted to not refactor the applicative code to make your code testable and so to add some tricks in the implementation to bypass the refactoring task.

    3) Test-Driven Development is code that works but you cannot forget the design aspects of your app classes and your test classes when you use it.

    A trivial example of how this might help would be if you're actually creating a new class instance in the middle of a method and assigning it to a private field: private field mocks won't help in that case because you are replacing the private field. But actually creating a real object might be very costly: you might want to replace it with a lightweight version when testing.

    I encountered such a situation yesterday, in fact... and my solution was to create a new package-private method called createXXX()... so I could mock it. But this in turn goes against the dictum "thou shalt not create methods just to suit your tests"!

    Using the package-private modifier is in some cases acceptable but it should be used only if all natural ways of designing your code don't allow to have an acceptable solution.

    "thou shalt not create methods just to suit your tests" may be misleading.

    In fact I would say rather : "thou shalt not create methods to suit your tests and that open the API of the application in an undesirable way"

    In your example, when you want to modify a dependency of your code that you would like mock or substitute a dependency during test, if you practice TDD you should not modify directly the implementation but start the modification by the test code.
    And if you test code seems blocked because you miss a constructor, a method, an object, etc... to set a dependency to your tested class, you are forced to add in your tested class.
    It is the TDD way.

    Above, I referred to not opening the API more than needed. I will give two examples that provide a way of setting a dependency but that don't open the API in the same way.

    This way of doing is desirable because the client cannot change the behavior of MyClass in production :

    @Service
    public class MyClass{
    ...
    MyDependency myDependency;
    ...
    @Autowired
    public MyClass(MyDependency myDependency){
       this.myDependency = myDependency;
    }
     ...
    }
    

    This way of doing is less desirable because the MyClass API grows while the applicative code doesn't need it. Besides with this new method, the client can change the behavior of MyClass in production by using the setter of myDependency field:

    @Service
    public class MyClass{
    ...
    MyDependency myDependency;
    ...
    @Autowired
    public void setMyDependency(MyDependency myDependency){
       this.myDependency = myDependency;
    }
     ...
    }
    

    Just a remark : if you have more than 4 or 5 arguments in your constructor, it may become cumbersome to use it.
    If it happens, using setters is still probably not the best solution as the root of the problem is probably that the class has too many responsibilities. So it should be refactored if it is the case.

提交回复
热议问题