Dieses Mal möchte ich vorstellen, wie man Code schreibt, der schwer zu testen ist. Die Sprache des Codes ist Java (bitte lesen Sie andere Sprachen).
Ich habe auch einen Artikel geschrieben, der das, was ich in diesem Artikel geschrieben habe, umgestaltet. https://qiita.com/oda-kazuki/items/b66fe3d4efec822497e6
Der Aggregationsgrad, der Bindungsgrad und der Grad der zyklischen Komplexität sind ebenfalls nachstehend zusammengefasst. https://qiita.com/oda-kazuki/items/a16b43dc624429de7db3
Diesmal wird die folgende Verarbeitung angenommen.
Zur Vereinfachung der Beschreibung ist außerdem die externe Bibliothek der folgenden Schnittstelle zu verwenden.
public class HttpClientFactory {
/**Gibt einen Client für die HTTP-Kommunikation zurück*/
static Client createClient(String domain);
}
public interface Client {
/**Holen Sie sich mit Web-API*/
HttpResult get(String path) throws HttpException;
/**Beitrag mit Web-API*/
HttpResult post(String path, Json json) throws HttpException;
}
public class HttpResult {
/**Holen Sie sich den JSON-Antworttext*/
public Json body();
}
public class JsonMapper<T> {
/**Serialisieren Sie das Objekt in JSON*/
public Json serialize(T obj);
/**Deserialisieren Sie JSON in ein Objekt*/
public T deserialize(Json json);
}
Zuerst erstellen wir einen Cache. Hier ist ** [Singleton] mit Entwurfsmuster (https://ja.wikipedia.org/wiki/Singleton_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3 ) Lass es uns mit Muster ** brillant machen. Ich bin cool, Designmuster zu verwenden.
Cache.java
public class Cache {
private static Cache instance = new Cahce();
//Verpflichtungspunkt 1:Effektive Nutzung der Karte
private Map<String,Object> user;
private String token;
private String accountId;
private Cache() {
}
public static Cache getInstance() {
return instance;
}
public void setUser(Map<String,Object> u) {
this.user = u;
}
public Map<String, Object> getUser() {
return this.user;
}
public void setToken(String a) {
this.token = a;
}
public String getToken() {
return this.token;
}
public String setAccountId(String accountId) {
this.accountId = accountId;
}
public String getAccountId() {
return this.accountId;
}
}
Als nächstes schreiben Sie den Prozess.
LoginService.java
public class LoginService {
//Verpflichtungspunkt 2:Innerhalb der Klasse initialisieren
private JsonMapper<Map<String,Object>> mapper = new JsonMapper<>();
//Verpflichtungspunkt 1:Verwenden Sie Mitgliedsvariablen vergeblich
private HttpException ex = null;
private Map<String,Object> user = null;
private Map<String,Object> body = null;
private String accessToken = null;
private Json json = null;
public Map<String,Object> login(String accessToken) {
//Verpflichtungspunkt 1:Elementvariablen wiederverwenden
this.accessToken = accessToken;
this.account = null;
//Verpflichtungspunkt 1:Lokale Variablen wiederverwenden
Map<String,Object> user = null;
this.ex = null;
boolean result = true;
this.json = null;
this.body = null;
if(this.accessToken != null) {
getCache().setToken(accessToken);
} else {
this.accessToken = getCache().getToken();
}
//Verpflichtungspunkt 3:Voller Verschachtelung
for (int i = 0; i < 3; i++) {
if (getCache().getUser() != null) {
result = this.logout();
if (result) {
try {
//Verpflichtungspunkt 1:Ich weiß nicht, wann der Wert in den Körper eingegeben wurde, indem ich hier nachschaue
this.getAccountId();
if (this.body != null) {
this.getAccount();
this.account = this.body;
if (this.body != null) {
getCache().setAccountId(this.body.get("accountId"));
this.getUser();
}
}
} catch (HttpException e) {
//Verpflichtungspunkt 1:Es ist nicht leicht zu lesen, deshalb bin ich süchtig danach, so zu kommentieren
//ex ist getAccount, getAccountId,Mit getUser gespeichert
if (this.ex != null) {
if(this.ex.getReason() == HttpException.Reason.TIMEOUT) {
Thread.sleep(1);
continue;
} else {
//Wegen eines anderen Fehlers ausgesetzt
break;
}
}
}
if(this.body != null) {
//Beenden Sie die Verarbeitung, da der Benutzer vergeben ist
user = this.body;
break;
}
} else {
//Unterbrochen, weil die Abmeldung fehlgeschlagen ist
this.body = null;
break;
}
}
try {
//Verpflichtungspunkt 1:TROCKEN? was ist das?
this.getAccountId();
if (this.body != null) {
this.getAccount();
if (this.body != null) {
getCache().setAccountId(this.body.get("accountId"));
this.getUser();
}
}
} catch (HttpException e) {
//ex ist getAccount, getAccountId,Mit getUser gespeichert
if (this.ex != null) {
if(this.ex.getReason() == HttpException.Reason.TIMEOUT) {
Thread.sleep(1);
continue;
} else {
//Wegen eines anderen Fehlers ausgesetzt
break;
}
}
}
if(this.body != null) {
//Beenden Sie die Verarbeitung, da der Benutzer vergeben ist
user = this.body;
}
break;
}
if(user != null) {
this.user = user;
//Verpflichtungspunkt 4:Ich verwalte den Cache und melde mich an
this.getCache().setUser(this.user);
}
return this.user;
}
//Verpflichtungspunkt 4:Methode anders als die ursprüngliche Rolle
private Cache getCache() {
//Verpflichtungspunkt 2:Beziehen Sie sich direkt auf Singleton
return Cache.getInstance();
}
//Verpflichtungspunkt 1:Eine Methode, die kein Getter mit einem Getter-ähnlichen Namen ist
//Verpflichtungspunkt 4:Methode anders als die ursprüngliche Rolle
private void getAccountId() throws HttpException {
try {
this.ex = null;
if(getCache().getAccountId() != null) {
//Verpflichtungspunkt 1:Verwenden Sie Mitgliedsvariablen auf verschiedene Weise
this.body = new HashMap<>();
this.body.put("accountId", getCache().getAccountId());
} else {
if(this.accessToken != null) {
//Verpflichtungspunkt 1:Verwenden Sie Mitgliedsvariablen auf verschiedene Weise
this.body = new HashMap<>();
this.body.set("token" , this.accessToken);
//Verpflichtungspunkt 2:Verwendung statischer Methoden&Rufen Sie WebAPI direkt auf
this.json = Http.createClient("https://account.example.com").post("/auth", this.mapper.serialize(body)).body();
this.body = this.mapper.deserialize(this.json);
}
}
} catch (HttpException e) {
this.body = null;
this.ex = e;
throw e;
}
}
//Verpflichtungspunkt 1:Eine Methode, die kein Getter mit einem Getter-ähnlichen Namen ist
//Verpflichtungspunkt 4:Methode anders als die ursprüngliche Rolle
private void getAccount() throws HttpException {
try {
this.ex = null;
//Verpflichtungspunkt 2:Verwendung statischer Methoden&Rufen Sie WebAPI direkt auf
this.json = Http.createClient("https://account.example.com").get("/accounts/" + this.body.get("accountId")).body();
this.body = this.mapper.deserialize(this.json);
} catch (HttpException e) {
this.body = null;
this.ex = e;
throw e;
}
}
//Verpflichtungspunkt 1:Eine Methode, die kein Getter mit einem Getter-ähnlichen Namen ist
//Verpflichtungspunkt 4:Methode anders als die ursprüngliche Rolle
private void getUser() throws HttpException {
try {
this.ex = null;
//Verpflichtungspunkt 2:Verwendung statischer Methoden&Rufen Sie WebAPI direkt auf
this.json = Http.createClient("https://example.com").post("/users", this.mapper.serialize(this.body)).body();
this.body = this.mapper.deserialize(this.json);
} catch (HttpException e) {
this.body = null;
this.ex = e;
throw e;
}
}
//Verpflichtungspunkt 4:Anmeldedienst, aber Sie können sich auch abmelden
public boolean logout() {
this.ex = null;
if (this.getCache().getUser() != null) {
this.user = this.getCache().getUser();
try {
//Verpflichtungspunkt 2:Verwendung statischer Methoden&Rufen Sie WebAPI direkt auf
Json json = Http.createClient("https://example.com").post("/logout", this.mapper.serialize(this.user)).body();
} catch (HttpException e) {
this.ex = e;
}
}
if (this.ex != null) {
this.user = null;
return false;
} else {
this.user = null;
//Verpflichtungspunkt 4:Melden Sie sich nicht nur ab, sondern löschen Sie auch den Cache
Cache.getInstance().setUser(null);
Cache.getInstance().setAccountId(null);
Cache.getInstance().setToken(null);
return true;
}
}
}
Ich legte etwas, das meinen Kopf flattern ließ. Wenn ich gebeten würde, dies zu testen, würde ich ein wenig weinen. Es scheint, dass ich auf eine höhere Höhe klettern kann, wenn ich die Punkte, die ich von nun an schreibe, gründlich umsetze, aber ich möchte nicht, dass jemand zu hoch klettert und es verliert, also werde ich es dieses Mal auf diesem Niveau belassen.
Dann werde ich die "Punkte erklären, die schwer zu testen sind", um die es mir besonders ging.
Der Zweck der Erstellung eines Komponententests besteht darin, zu bestätigen, dass er "gemäß den Spezifikationen" eine wichtige Voraussetzung ist. Daher ist es notwendig, das erwartete Ergebnis in Form von "Ich weiß nicht, welche Art von Verarbeitung im Code geschrieben ist" zu bestätigen. Wenn Sie dies nicht tun, fallen Sie in das Test-Anti-Muster, einfach "** den geschriebenen Code zu überprüfen und nicht die Tests durchzuführen, die Sie wirklich benötigen **".
Um den Komponententest zu bestehen, muss jedoch bekannt sein, welche Art von Verarbeitung in der Methode in gewissem Umfang durchgeführt wird. Dies liegt daran, dass die Prozesse außerhalb der Klasse, die nicht das Ziel des Tests sind, beim Verspotten erstellt werden.
Daher ist es ziemlich schwierig, einen Test nur durch Nichtlesen durchzuführen. Dieses Mal habe ich die Lesbarkeit durch die folgende Methode reduziert.
Um am Ende ein wenig hinzuzufügen, geht getAccount () beispielsweise davon aus, dass Sie getAccountId () aufrufen, und dies allein schlägt fehl. Wenn Sie den Prozess richtig verstehen möchten, müssen Sie daher hier und da den Code überprüfen, was zu einer schlechten Lesbarkeit beiträgt.
Alle in diesem LoginService beschriebenen Methoden werden mit der Methode login () kombiniert. Dies hängt direkt mit der Schwierigkeit des Testens zusammen.
Um eine enge Verbindung herzustellen, haben wir Folgendes getan:
Wenn Sie diese verwenden, können Sie den Test erschweren, es sei denn, Sie machen ihn beim Testen mit "PowerMock" usw. zwangsweise zum Verspotten. Wenn es sich um eine Sprache handelt, die nicht zwangsweise in Mock konvertiert werden kann, bleibt keine andere Wahl, als die Web-API-Verbindung aufzugeben und zuzulassen. In diesem Fall schlägt der Test jedoch je nach Status fehl, da er von den Daten auf der Webserverseite abhängt. Ich bin taub. ~~ Eigentlich wollte ich Steuerkopplung usw. einbauen, aber ich hörte auf, weil es problematisch war ~~.
Aufgrund der vergeblichen Verwendung von if-Anweisungen usw. beträgt die zyklische Komplexität etwa 20 (obwohl sie nicht richtig gemessen wird).
Dies bedeutet, dass Sie mindestens 20 Muster testen müssen. Um die richtige Route zu finden, müssen Sie sie zu einem Mock machen, als würden Sie eine Nadel einfädeln. Eine private Methode erhöht ebenfalls die Schwierigkeit. Sie können sehen, wie verrückt es wird, wenn es erstellt wird.
Dieser Anmeldedienst verfügt über die gesamte für die Anmeldung erforderliche Verarbeitung, und das Finish ist in der Aggregation sehr gering. Da die Anzahl der Zeilen jetzt noch gering ist, ist es umständlich, sie als Gottklasse zu bezeichnen, aber es besteht die Möglichkeit.
Dieser Anmeldedienst hat mindestens die folgenden Rollen:
Mit diesen zusammen möchten Sie nur Ihren Anmeldefluss testen, müssen jedoch Web-API-Anforderungen, Abmeldelogik, Caching und alle Arten von Rauschen berücksichtigen. verlieren. Darüber hinaus verringert der hohe Kopplungsgrad den SAN-Wert beim Schreiben von Tests.
Nächstes Mal werde ich versuchen, diesen Code ein wenig einfacher zu testen.
Recommended Posts