[JAVA] Was sind die Nebenwirkungen? ~ Lassen Sie uns einfach einen kleinen Unit-Test durchführen ~

Was sind die Nebenwirkungen?

Ein Nebeneffekt bei der Programmierung ist, dass eine Funktion den (logischen) Zustand eines Computers ändert. Beeinflusst die danach erzielten Ergebnisse. Ein typisches Beispiel ist die Zuordnung von Werten zu Variablen.

[wikipedia](https://ja.wikipedia.org/wiki/%E5%89%AF%E4%BD%9C%E7%94%A8_(%E3%83%97%E3%83%AD%E3% Von 82% B0% E3% 83% A9% E3% 83% A0))

Mit anderen Worten?

Es ist ein Prozess, der nicht immer das gleiche Ergebnis zurückgibt, selbst wenn er auf die gleiche Weise aufgerufen wird (grob gesagt).

Was ist los?

** Schwierig zu testen! !! ** ** ** (In diesem Artikel wird das automatische Testen des Quellcodes mit einem Testframework wie "JUnit" als Unit-Test bezeichnet.)

Es verursacht viel Ärger

Zeigen Sie mir ein Beispiel

Ich wollte einen Unit-Test dieser Klasse machen, aber es scheint unmöglich.

Greet.java


@AllArgsConstructor
public class Greet {
    String first;
    String last;
    String gender;

    public void greet() {
        System.out.println(
                getFirstMessage() + forFullName() + getForGender()
        );
    }

    private String getFirstMessage() {
        int hour = LocalDateTime.now().getHour();
        if (6 <= hour && hour < 12) {
            return "Guten Morgen";
        } else if (12 <= hour && hour < 18) {
            return "Hallo";
        } else {
            return "Gute Nacht";
        }
    }

    private String getForGender() {
        if (gender.equals("M")) {
            return "Kun";
        } else {
            return "Chan";
        }
    }

    private String forFullName() {
        return first + " " + last;
    }
}

Main.java


public class Main {
    public static void main(String[] args) {
        Greet greet = new Greet("Yamada", "Takashi", "M");
        greet.greet();
    }
}

Wo ist es schlecht

Aus diesen Gründen denke ich, dass dieser "Gruß" voller Nebenwirkungen ist

Geben Sie mir ein Beispiel für Nebenwirkungen

Verarbeitung, die nicht immer das gleiche Ergebnis liefert, auch wenn sie auf die gleiche Weise aufgerufen wird

Selbst wenn Sie es beispielsweise auf die gleiche Weise wie "greet.greet ();" ausführen, unterscheidet sich das Ergebnis je nach Ausführungszeitpunkt.

Was wird angezeigt? Ich weiß nicht, wann das gemacht wird ** wann **!

Wenn Sie beispielsweise Folgendes tun, ist der Code in der 2. und 4. Zeile genau gleich, das Ergebnis ist jedoch unterschiedlich.

Main.java


Greet greet = new Greet("Yamada", "Takashi", "M");
greet.greet();
greet.gender = 'F';
greet.greet();

Wie hängt das damit zusammen, dass man nicht testen kann?

Wenn Sie beispielsweise unten morgens einen Test schreiben, ist klar, dass er nachts fehlschlagen wird.

greet.greet() == 'Hallo Takashi Yamada'

Außerdem ist greet () in erster Linie void Es gibt mir nicht einmal die Möglichkeit, Werte zu vergleichen, da ich mit der Standardausgabe zufrieden bin und nichts zurückgibt.

(Natürlich können Sie es nicht genau tun, aber Sie benötigen einen Hack für die aktuelle Zeit und einen Hack für die Standardausgabe.)

Versuchen Sie dann, das Problem zu beheben

Natürlich werde ich es reparieren Dieser Code ist nicht akzeptabel (obwohl persönlich)

Schauen Sie sich Greet an und organisieren Sie, was Sie tun

Machst du 5 Dinge?

  1. Ändern Sie das erste Wort im Laufe der Zeit
  2. Ändern Sie das letzte Wort je nach Geschlecht
  3. Kombinieren Sie den Vor- und Nachnamen mit einer halben Breite
  4. Kombinieren Sie sie alle mit einem halben Winkel
  5. Standardausgabe

In erster Linie ** Zeit ** und ** Ausgabe **

Wenn es um Unit-Tests geht, besteht das Problem darin, wie 1 und 5 implementiert werden.

Sie sollten die Kampagne nur samstags sehen, den Strom nachts ausschalten, die Stapelverarbeitung am Anfang des Monats beginnen, beurteilen, ob sie innerhalb der Geschäftszeiten des Partnerunternehmens liegt usw. In der Regel sollte die Zeit von außerhalb der Entscheidungslogik vergehen (abhängig von der Schichtstruktur und dem Komponentendesign). In diesem Beispiel können Sie Greet nicht testen, ohne die Zeit von Main zu vertreiben.

Wenn das zusammengesetzte Ergebnis dann standardmäßig ausgegeben wird, kann es nicht getestet werden. Das Ergebnis der Logik muss also zurückgegeben werden

Lass es uns reparieren

Greet.java


@AllArgsConstructor
public class Greet {
    String first;
    String last;
    String gender;
    LocalDateTime now; //Holen Sie es sich, ohne es selbst zu generieren

    public String greet() {
        return getFirstMessage() + forFullName() + getForGender(); //Rückkehr
    }

    private String getFirstMessage() {
        int hour = now.getHour();
        if (6 <= hour && hour < 12) {
            return "Guten Morgen";
        } else if (12 <= hour && hour < 18) {
            return "Hallo";
        } else {
            return "Gute Nacht";
        }
    }

    private String getForGender() {
        if (gender.equals("M")) {
            return "Kun";
        } else {
            return "Chan";
        }
    }

    private String forFullName() {
        return first + " " + last;
    }
}

Wenn Sie die beiden oben genannten Probleme beheben, können Sie einen Test schreiben, der besagt: "** Wenn es jetzt X Uhr ist, sollte es zurückkommen **".

GreetTest.groovy


class GreetTest extends Specification {
    def test_1() {
        setup:
        def greet = new Greet("Yamada", "Takashi", "M", LocalDateTime.of(2017, 7, 18, 12, 30, 00))

        expect:
        greet.greet() == 'Hallo Takashi Yamada'
    }

    def test_2() {
        setup:
        def greet = new Greet("Yamada", "Takashi", "M", LocalDateTime.of(2017, 7, 18, 22, 30, 00))

        expect:
        greet.greet() == 'Gute Nacht Takashi Yamada'
    }
}

Feldzugang

Korrigieren Sie übrigens den Punkt, an dem die "private" Methode auf das Feld in der Methode zugreift. Dies ist nicht immer der Fall und möglicherweise objektorientierter, aber ich schreibe normalerweise den Code für das feste Beispiel.

Der Vorteil der Korrektur besteht darin, den relevanten Wert zu klären und die Möglichkeit auszuschließen, dass der Status des Feldes in "privat" aktualisiert wurde. (Ich werde später ein Beispiel zeigen, aber ich werde es zu einer "privaten statischen" Methode machen und den Feldzugriff unmöglich machen.)

Greet.java


@AllArgsConstructor
public class Greet {
    String first;
    String last;
    String gender;
    LocalDateTime now;

    public String greet() {
        return getFirstMessage(now) + forFullName(first, last) + getForGender(gender);
    }

    private String getFirstMessage(LocalDateTime now) {      //Verwenden Sie nur den angegebenen Wert als Argument
        int hour = now.getHour();
        if (6 <= hour && hour < 12) {
            return "Guten Morgen";
        } else if (12 <= hour && hour < 18) {
            return "Hallo";
        } else {
            return "Gute Nacht";
        }
    }

    private String getForGender(String gender) {             //Ähnlich
        if (gender.equals("M")) {
            return "Kun";
        } else {
            return "Chan";
        }
    }

    private String forFullName(String first, String last) {  //Ähnlich
        return first + " " + last;
    }
}

Es ist jetzt einfacher zu erkennen, von welchem Wert jede "private" Methode nur abhängt Übrigens hängt jede "private" Methode nur vom Argument ab und gibt das Ergebnis zurück Mit anderen Worten, die Nebenwirkungen des "privaten" Methodenteils sind verschwunden!

Die feste "private" Methode hat keinen Feldzugriff Es ist also möglich, es zu einer "statischen" Methode zu machen

Wenn "statisch" angehängt ist, kann "dies" nicht verwendet werden. "Ich werde während der Berechnung einige Flags setzen." Es ist garantiert, dass Felder wie

Angesichts der Bedeutung des Wortes "statisch" ändert sich das Ergebnis je nach Status nicht dynamisch. Können Sie sich also irgendwie erleichtert fühlen?

(Die statische Umwandlung wird zusammen mit der folgenden Verbesserung beschrieben.)

Statisierung

Nun, endlich das Ziel Nur greet () hängt vom Feld ab, aber Sie müssen die Werte nicht mehr separat im Feld speichern.

Greet.java


public class Greet {

    //Das Feld ist weg

    public static String greet(String first, String last, String gender, LocalDateTime now) { //Das ist das ganze Argument
        return getFirstMessage(now) + forFullName(first, last) + getForGender(gender);
    }

    private static String getFirstMessage(LocalDateTime now) {
        int hour = now.getHour();
        if (6 <= hour && hour < 12) {
            return "Guten Morgen";
        } else if (12 <= hour && hour < 18) {
            return "Hallo";
        } else {
            return "Gute Nacht";
        }
    }

    private static String getForGender(String gender) {
        if (gender.equals("M")) {
            return "Kun";
        } else {
            return "Chan";
        }
    }

    private static String forFullName(String first, String last) {
        return first + " " + last;
    }

    //Alle Methoden sind jetzt statisch
}

Da Sie jedoch nicht mehr "neu" ausführen müssen, müssen Sie auch den Anrufer ändern.

GreetTest.groovy


class GreetTest extends Specification {
    def test_1() {
        expect:
        Greet.greet("Yamada", "Takashi", "M", LocalDateTime.of(2017, 7, 18, 12, 30, 00)) == 'こんにちは Yamada Takashi くん'
    }

    def test_2() {
        expect:
        Greet.greet("Yamada", "Takashi", "M", LocalDateTime.of(2017, 7, 18, 22, 30, 00)) == 'おやすみ Yamada Takashi くん'
    }
}

Jetzt wird dieser Test jederzeit bestanden, und ich kann ihn nicht einfach mit diesem Argument aufrufen, um ein weiteres Ergebnis zu erhalten!

Im Gegensatz zur Objektorientierung! ??

Erstens bin ich davon angezogen, Nebenwirkungen aufgrund des Einflusses funktionaler Sprachen zu eliminieren, aber ich denke nicht einmal, dass es eine Meinung ist, also habe ich einen Moment darüber nachgedacht. Gibt es 3 Arten? Nun, dies ist ein Lehrbuch, das ich mir gerade ausgedacht habe. 1. Eine Klasse, die keinen Wert hat und basierend auf dem empfangenen Wert beurteilt oder berechnet. 2. Eine Klasse, die einen Wert hat und sich basierend auf ihrem eigenen Wert verhält 3. Klassen, die damit umgehen

Schauen wir uns jeden an

Unnötig zu erwähnen, dass die Top-Klasse die "Greet" -Klasse ist, die ich zuvor festgelegt habe. Es hat keinen eigenen Wert, es hängt nur von seinen Argumenten ab

Was ist die zweite Klasse Ich denke, dies ist die objektorientierteste Klasse, aber es ist eine Klasse, die "Dinge" wie "persönlicher Name" und "Geschlecht" ausdrückt.

Lassen Sie es im vorherigen Thema erscheinen.

First.java


@AllArgsConstructor
public class First {
    @Getter
    private final String value;
}

Last.java


@AllArgsConstructor
public class Last {
    @Getter
    private final String value;
}

Gender.java


public enum Gender {
    M, F
}

User.java


@AllArgsConstructor
public class User {
    private final First first;
    private final Last last;
    private final Gender gender;

    public String asFullNameString() {
        return first.getValue() + " " + last.getValue();
    }

    public boolean isM() {
        return gender == Gender.M;
    }
}

Ich habe eine Klasse erstellt, die "Person" namens "Benutzer" darstellt, und versucht, das Geschlechtsurteil und die vollständige Verkettung von Namen dort zu verschieben.

Ich denke, dass dies tatsächlich eine ziemlich gute Aufgabenteilung ist Der Grund ist, dass sich herausstellte, dass die Teile wie "Begrüßen" "Geschlecht mit" M "vergleichen" und "Vor- und Nachname mit halber Breite verbinden" nicht wirklich mit Begrüßungen zusammenhängen. Dies liegt daran, dass für Begrüßungen lediglich "der Vor- und Nachname ein wenig nach Zeit und Geschlecht verarbeitet werden muss" und "männlich ist" M "oder" Verbindung mit halber Breite "ein Prozess ist, der der" Definition einer Person "entspricht. (Natürlich kann dies je nach Spezifikation und Design nicht der Fall sein.)

Und natürlich ist es ein Unit-Test von "User", das ist absolut

Wenn Sie "Men Judgement" und "Last Name Concatenation" auf "User" verschieben und einen Unit-Test von "User" durchführen, wird der Test von "Greet" nur aus Zeitzonen-Urteil und Kun-Chan bestehen.

Greet.java


public class Greet {
    public static String greet(User user, LocalDateTime now) {
        return getFirstMessage(now) + user.asFullNameString() + getForGender(user);
    }

    private static String getFirstMessage(LocalDateTime now) {
        int hour = now.getHour();
        if (6 <= hour && hour < 12) {
            return "Guten Morgen";
        } else if (12 <= hour && hour < 18) {
            return "Hallo";
        } else {
            return "Gute Nacht";
        }
    }

    private static String getForGender(User user) {
        if (user.isM()) {
            return "Kun";
        } else {
            return "Chan";
        }
    }
}

Greet ist wieder erfrischend

  1. Klassen, die damit umgehen

Und last but not least entspricht es diesmal "Main".

Ich habe einen User erstellt und ihn mit Greet berechnen lassen (Code wird weggelassen)

Zusammenfassung von 3 Typen

Mit anderen Worten, es könnte so aussehen

  1. Logik 2 Dinge
  2. Verarbeitung

Die Logik ist grundsätzlich "statisch" und sollte nicht zustandsabhängig sein

Dinge enthalten Werte und verhalten sich basierend auf ihnen, verbergen jedoch die Werte selbst und die Logik selbst (Entspricht dies der Kapselung?)

Es gibt eine Behandlungsschicht, in der beide zusammenarbeiten können. Nebenwirkungen sind nur in der Behandlungsschicht zulässig (Da Nebenwirkungen nicht beseitigt werden können, werde ich es hier tun. Dieser Klassentest wird mit einem Mock usw. durchgeführt, aber er wird bald wieder durchgeführt.)

Beiseite? Über die Grenze zwischen Logik und Dingen

Ist es die Entscheidung von "Greet" zu sagen "wenn Sie ein Mann sind, wenn Sie eine Frau sind"? Ist es nicht "Benutzer"?

" User wird mit einem Leerzeichen halber Breite kombiniert, aber beim Senden einer E-Mail muss es mit einem Leerzeichen voller Breite kombiniert werden. "

Du denkst vielleicht Ich dachte einen Moment nach

Als ich dachte, das sei "Logik" oder "Ding?", Machte ich schließlich "Dinge, die der Logik gewidmet sind", aber vor kurzem habe ich mich beruhigt.

Mit anderen Worten, erstellen Sie "Benutzer" für "Begrüßen" und schreiben Sie "Kun-chan" und "Kombination mit halber Breite" in die auf Begrüßung spezialisierte Benutzerklasse. Wenn es für E-Mails anders ist, richten wir eine separate E-Mail-spezifische Benutzerklasse ein.

Ich denke, dies ist eine weitere Aufgabenteilung oder eine klarere Grenze. Wenn Sie dies tun, wird die Anzahl der Klassen zunehmen, aber ich bin der Meinung, dass die Anzahl der Testobjekte nicht so stark zunehmen wird und vor allem die Abhängigkeit abnehmen wird.

Sie müssen nicht so etwas wie "Begrüßungen aufgrund von Änderungen in den E-Mail-Spezifikationen neu bewerten"!

Zusammenfassung

Beispiele für Nebenwirkungen

Seien Sie vorsichtig, wenn Sie diesen Bereich sehen! !!

Wenn Sie referenzieren, tun Sie dies außerhalb von "Logik, Ding" und "Logik, Ding" und dürfen nicht davon abhängen Schreiben Sie den zusammengestellten Wert nicht an Ort und Stelle, geben Sie ihn einmal in die Verarbeitungsschicht zurück und schreiben Sie ihn erneut in die Verarbeitungsschicht

Einfache Beurteilung

Um es einfach auszudrücken: Wenn der Test "Logik, Ding" Folgendes erfordert, werden die Nebenwirkungen gemischt.

Wenn diese gründlich implementiert werden, nimmt die Anzahl der schwer zu bewertenden Codes ab, die Anzahl der Komponententests nimmt zu und das Entwicklungstempo wird verbessert.

Wir sehen uns wieder

Recommended Posts

Was sind die Nebenwirkungen? ~ Lassen Sie uns einfach einen kleinen Unit-Test durchführen ~
Was tun, wenn im Testcode der Steuereinheit in Rails der Fehler "302" angezeigt wird?
Lassen Sie uns den Unit-Test wie das Atmen heilen (Apex Trigger Edition)
Was tun, wenn die Änderungen im Servlet nicht berücksichtigt werden?