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))
Es ist ein Prozess, der nicht immer das gleiche Ergebnis zurückgibt, selbst wenn er auf die gleiche Weise aufgerufen wird (grob gesagt).
** 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
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();
}
}
Aus diesen Gründen denke ich, dass dieser "Gruß" voller Nebenwirkungen ist
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();
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.)
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?
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'
}
}
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.)
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!
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
- 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)
Mit anderen Worten, es könnte so aussehen
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.)
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"!
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
Um es einfach auszudrücken: Wenn der Test "Logik, Ding" Folgendes erfordert, werden die Nebenwirkungen gemischt.
Json
, Einfügen von Datenbank-Dummy-Daten usw.void
Methode in" Logik, Sache "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