Do I need a test if I do DDD in a language with types?

At a previous DDD roundtable, the question "If you're doing DDD in a static language, don't you need a test?" Was asked, and more people were surprised to hear "yes / maybe". So I decided to sort it out a bit.

In conclusion, my theory is, "Of course, the benefits of compilation are great, but stopping thinking doesn't solve everything, and there is a lot of learning, so it's better to write."

Conditional branching is prone to bugs even if it has a type

For example, a modification that increases ʻelse if` is rather dangerous if there is only a type.

Plan.java


public enum Plan {
    PLAN_1,
    PLAN_2,
    PLAN_3;
}

Price.java


public class Price {
    private final int value;

    public static Price of(Plan plan) {
        if (plan == Plan.PLAN_1)
            return new Price(100);

        else if (plan == Plan.PLAN_2)
            return new Price(200);

        else
            return new Price(300);
    }
}

I added PLAN_4 to this.

I wanted the price to be 400 yen, but I forgot to add ʻelse if`.

public enum Plan {
    PLAN_1,
    PLAN_2,
    PLAN_3,
+   PLAN_4;
}

Of course, the compilation goes through, and PLAN_4 costs 300 yen.

scared. very scary.

Digression

It's a silly example, but it's not a joke.

It is not uncommon for products to remember to add ʻelse if, such as price, detailed item, email text, system code, etc. according to Plan`. ** It is rather reckless to prevent this kind of leakage on a large scale. ** **

Then what do you do

Writing a test should be a little better.

If all the elements of the enum (Plan.values ()) are well entwined in the test code, it can be detected.

PriceTest.groovy


//The test code used groovy and spock

class PriceTest extends Specification {
    def "#plan -> #exp"() {
        expect:
        Price.of(plan) == new Price(exp)

        where:
        plan        || exp
        Plan.PLAN_1 || 100
        Plan.PLAN_2 || 200
        Plan.PLAN_3 || 300
    }

    def guard() {
        expect:
        Plan.values().length == 3
    }
}

It's simple, but if a test that depends on Plan contains == 3, you'll notice when Plan increases or decreases.

I don't use ʻelse` for price judgment.

Price.java


        else if (plan == Plan.PLAN_3)
            return new Price(300);
        
        else
            throw new RuntimeException("match error");

If you have test code, you will notice it.

Organize: I'm scared here

Argument fraud may be beneficial

On the contrary, if you do it well, you can prevent such bugs perfectly.

Example 1

MailService.java


public class MailService {
    public void send(String itemName, String userName) {
        String mailBody = MailBodyFactory.create(itemName, userName);
        ...
    }
}

Example 2

FooService.java


public class FooService {
    FooRepository fooRepository;

    public void replace(Foo usingOne) {
        Foo newOne = Foo.newOne();
        
        fooRepository.replace(newOne, usingOne);
    }
}

What's wrong is ...

Example 1

MailFactory.java


public class MailBodyFactory {
    public static String create(String userName, String itemName) {
        return String.format("%s%Thank you for purchasing s", userName, itemName);
    }
}

Example 2

FooRepository.java


public interface FooRepository {
    void replace(Foo usingOne, Foo newOne);
}

The order of the arguments was reversed.

Another digression

Did you think "Do you know!"? But most of the time, I don't remember the order of the argument of the product code, and it is unexpectedly very tiring to implement it carefully.

In addition, this kind of thing is very bad because it moves poorly.

Then what do you do

Don't abuse String and create classes for ʻUserName and ʻItemName. Then you can never reverse it.

Foo seems to have created a domain class, but let's divide it by state, for example.

public interface FooRepository {
-   void replace(Foo usingOne, Foo newOne);
+   void replace(UsingFoo usingFoo, NewFoo newFoo);
}

This will never be the opposite. (For the class by state, I wrote this article together with momentum → [Let's stop the god entity that expresses all the status in one class!](Https://qiita.com/suzuki-hoge/items / d3ea5940898d85bbc03e)))

It's a rough guide, but I think it's better to type ** where you're trying to explain with variable names **.

It's much easier to get the red line, and the editor also thinks wisely about complement candidates.

By the way, I wrote a similar story before, so if you are interested, please take a look there as well. It feels good to have different classes for the same table values

Organize: I'm scared here

You may find it difficult to use if you have a test

For example, this code

CampaignCode.java


public enum CampaignCode {
    CODE_A, CODE_B;

    public static CampaignCode create() {
        if (LocalDateTime.now().getDayOfMonth() < 15) {
            return CODE_A;
        } else {
            return CODE_B;
        }
    }
}

When I try to write test code, now gets in the way and I should say" Oh, I can't write the test ... ?? ".

This means that the product you make will only work with now, so it will interfere with the testing of other parts, and it will not be flexible in mocking and integration tests.

** The test code uses that method first **, so it's a good chance to notice something like "that? Is this method difficult to use?"

(In this example, now should be passed as an argument)

Summary

in conclusion

For example, in the case of enum, warnings and compilation errors are issued depending on the language, but I thought that the main flow of the venue was "Does compilation eliminate the need for test code?", So I thought about writing it as an antithesis. I did.

This article is my personal theory.

If you want to imitate the words of a person you heard recently, if you think "I heard you don't need a test, is it really?", Write the test obediently. I think. That's it.

Recommended Posts

Do I need a test if I do DDD in a language with types?
I made a primality test program in Java
I wrote a primality test program in Java
Output true with if (a == 1 && a == 2 && a == 3) in Java (Invisible Identifier)
I wrote a test with Spring Boot + JUnit 5 now
I want to Flash Attribute in Spring even if I set a reverse proxy! (do not do)
What to do if you get a "302" error in your controller unit test code in Rails
I wrote a CRUD test with SpringBoot + MyBatis + DBUnit (Part 1)
What to do if you get a java.io.IOException in GlassFish
I searched for a web framework with Gem in Ruby
Even in Java, I want to output true with a == 1 && a == 2 && a == 3
I came across a guy with two dots in Rails
What to do if you get a gcc error in Docker
What if I write a finally clause in the try-with-resources syntax?
I can't create a Java class with a specific name in IntelliJ
Create a parent-child relationship form with form_object (I also wrote a test)
I recently made a js app in the rumored Dart language
What to do if you get a DISPLAY error in gym.render ()
I created a PDF in Java.
I made a GUI with Swing
What to do if you get a groovy warning in Thymeleaf Layout
I want to select multiple items with a custom layout in Dialog
Even in Java, I want to output true with a == 1 && a == 2 && a == 3 (PowerMockito edition)
I tried to build a Firebase application development environment with Docker in 2020
I can't build if I set the build destination to a simulator with XCode12!
I wanted to implement a slide show in a fashionable way with slick.
I want to display a PDF in Chinese (Korean) with thin reports
I wrote a Lambda function in Java and deployed it with SAM
I want to ForEach an array with a Lambda expression in Java