Je le laisse comme mémo en me souvenant du code que j'ai vu récemment et du processus de réparation moi-même. Je suis désolé si je ne comprends pas.
Comme le titre l'indique, j'ai passé beaucoup de temps à vérifier l'opération tout en essayant d'utiliser du code dépendant, donc je l'enregistre comme une réflexion. (J'ai entendu dire que Kotlin est populaire, alors je l'ai écrit bien que ce soit un code pour débutant.)
«SampleFileReader» et «SampleFileWriter» sont le code fourni à l'origine. ** Le contenu est texto, veuillez donc le considérer comme aucun problème ici **
//Classe à améliorer
class SampleManager(private val reader: SampleFileReader,
private val writer: SampleFileWriter) {
fun doSomething1() {
reader.read()
writer.writeLine("Kitto faire quelque chose1")
}
fun doSomething2() {
reader.read()
writer.writeLine("Kitto faire quelque chose2")
}
}
//Classe à améliorer
class WorkFlow {
private val readPath: String = "read.txt"
private val writePath: String = "write.txt"
//Une instance de SampleManager est disponible.
//Je veux garder les dépendances séparées pour des tests faciles.
private val manager: SampleManager
get() {
val reader = SampleFileReader(readPath)
val writer = SampleFileWriter(writePath)
return SampleManager(reader, writer) //
}
//Traitement à l'aide d'une instance de SampleManager
fun output() {
manager.doSomething1()
manager.doSomething2()
}
}
class SampleFileReader(private val filePath: String) {
private val file: File?
get() = Paths.get(filePath)?.toFile()
fun read(): Iterator<String> {
val br = BufferedReader(InputStreamReader(FileInputStream(file)))
return br.readLines().iterator()
}
}
class SampleFileWriter(private val filePath: String) {
private val bw = BufferedWriter(OutputStreamWriter(FileOutputStream(file), "UTF-8"))
private val file: File?
get() = Paths.get(filePath)?.toFile()
fun writeLine(str: String) = bw.write(str)
fun close() = bw.close()
}
Les problèmes ici sont définis comme 1 et 2 ci-dessous.
[problème]
SampleManager
dans la classe WorkFlow
, mais quand je considère la classe WorkFlow
comme une cible de test, cela dépend de ** SampleManager
, donc elle est testée. Il y a un problème que c'est difficile **.Donc, en considérant le test WorkFlow, j'ai inventé un peu le processus d'instanciation SampleManager
pour séparer les dépendances.
La politique était de créer une instance SampleManager
dans la classe WorkFlow
et de rendre la classe WorkFlow
** héritable ** afin que le code de test puisse définir l'instance SampleManager
pour le test.
Plus précisément, les mesures suivantes ont été prises.
pour rendre la classe
WorkFlow` héritablecreateManager ()
pour l'instanciation avec protected open
.WorkFlow
, nous avons défini la classe TestWorkFlow
à tester comme une sous-classe et avons remplacé la méthodecreateManager ()
.Ne prenez pas la peine de rendre le code de production «ouvert». Je pensais que, mais dans mon entreprise, il semblait qu'il n'y aurait pas de problème même si je le changeais en «open», alors j'ai utilisé «open» et en ai hérité.
//Cela sera amélioré à la prochaine étape
class SampleManager(private val reader: SampleFileReader,
private val writer: SampleFileWriter) {
fun doSomething1() {
reader.read()
writer.writeLine("Kitto faire quelque chose1")
}
fun doSomething2() {
reader.read()
writer.writeLine("Kitto faire quelque chose2")
}
}
//Kotlin n'est pas héritable par défaut, donc open est ajouté.
open class WorkFlow {
private val readPath: String = "read.txt"
private val writePath: String = "write.txt"
private val manager: SampleManager
get() {
//Utiliser la méthode d'usine
return createManager()
}
fun output() {
manager.doSomething1()
manager.doSomething2()
}
//Extrait comme méthode d'usine
//Modificateurs pouvant être remplacés dans les sous-classes`protected open`À
protected open fun createManager(): SampleManager {
val reader = SampleFileReader(readPath)
val writer = SampleFileWriter(writePath)
return SampleManager(reader, writer)
}
}
//Classe à tester au lieu de WorkFlow
class TestWorkFlow : WorkFlow() {
override fun createManager(): SampleManager {
TODO("Créer une instance de SampleManager pour le test")
}
}
Cependant, quand j'ai essayé d'écrire la partie TODO pour renvoyer réellement le SampleManager pour le test,
Une erreur se produira si le chemin du fichier référencé par le «SampleFileReader» dont dépend le «SampleManager» n'existe pas.
J'aimerais pouvoir créer une instance de SampleManager
, mais j'ai également besoin deSampleFileReader
et de SampleFileWriter
, qui est fortement dépendant.
class TestWorkFlow : WorkFlow() {
override fun createManager(): SampleManager {
val reader = SampleFileReader("Vous devez spécifier un chemin existant pour lire")
val writer = SampleFileWriter("Écrire le chemin de destination")
return SampleManager(reader, writer)
}
}
//J'espère que vous pouvez imaginer l'instanciation dans le code de test...
fun main(args: Array<String>) {
val flow = TestWorkFlow()
flow.output()
}
Donc, je vais l'améliorer un peu en réponse au problème 2.
Ici, j'ai pensé à la politique d'extraction du comportement de SampleManager
en tant qu'interface.
//Extraire le comportement en tant qu'interface
interface SampleManager {
fun doSomething1()
fun doSomething2()
}
//Renommé la classe avec SampleManager en SampleManagerImpl
class SampleManagerImpl(private val reader: SampleFileReader,
private val writer: SampleFileWriter) : SampleManager {
override fun doSomething1() {
val list = reader.read()
println(list)
writer.writeLine("Kitto faire quelque chose1")
}
override fun doSomething2() {
reader.read()
writer.writeLine("Kitto faire quelque chose2")
}
}
Le code de test est ci-dessous.
//Pseudo classe pour les tests. Implémentation de SampleManager.
//Il n'est pas nécessaire que ce soit une classe de données, cela peut être une classe ordinaire.
data class FakeSampleManager(val prop1: String = "alice",
val prop2: String = "bob") : SampleManager {
override fun doSomething1() = println("$prop1 $prop1 $prop1")
override fun doSomething2() = println("$prop2 $prop2 $prop2")
}
//Classe à tester au lieu de WorkFlow
class TestWorkFlow : WorkFlow() {
override fun createManager(): SampleManager {
//Modifié pour renvoyer un pseudo objet pour le test.
return FakeSampleManager()
}
}
Le code ci-dessus ne nécessite pas «SampleFileReader» et «SampleFileWriter».
Cela a résolu le problème de "J'aimerais pouvoir créer une instance de" SampleManager ", mais j'avais également besoin de" SampleFileReader "et de" SampleFileWriter ", qui est très dépendante" pendant les tests.
Pour référence, je vais mettre tout le code ici.
** Code de production **
class SampleFileReader(private val filePath: String) {
private val file: File?
get() = Paths.get(filePath)?.toFile()
fun read(): List<String> {
val br = BufferedReader(InputStreamReader(FileInputStream(file)))
return br.readLines()
}
}
class SampleFileWriter(private val filePath: String) {
private val bw = BufferedWriter(OutputStreamWriter(FileOutputStream(file), "UTF-8"))
private val file: File?
get() = Paths.get(filePath)?.toFile()
fun writeLine(str: String) = bw.write(str)
fun close() = bw.close()
}
interface SampleManager {
fun doSomething1()
fun doSomething2()
}
class SampleManagerImpl(private val reader: SampleFileReader,
private val writer: SampleFileWriter) : SampleManager {
override fun doSomething1() {
val list = reader.read()
println(list)
writer.writeLine("Kitto faire quelque chose1")
}
override fun doSomething2() {
reader.read()
writer.writeLine("Kitto faire quelque chose2")
}
}
open class WorkFlow {
private val readPath: String = "read.txt"
private val writePath: String = "write.txt"
private val manager: SampleManager
get() {
return createManager()
}
fun output() {
manager.doSomething1()
manager.doSomething2()
}
protected open fun createManager(): SampleManager {
val reader = SampleFileReader(readPath)
val writer = SampleFileWriter(writePath)
return SampleManagerImpl(reader, writer)
}
}
** pour test **
data class FakeSampleManager(val prop1: String = "alice",
val prop2: String = "bob") : SampleManager {
override fun doSomething1() = println("$prop1 $prop1 $prop1")
override fun doSomething2() = println("$prop2 $prop2 $prop2")
}
//Classe à tester au lieu de WorkFlow
class TestWorkFlow : WorkFlow() {
override fun createManager(): SampleManager {
return FakeSampleManager()
}
}
//Instanciation pendant le test
fun testXX {
val flow = TestWorkFlow()
flow.output()
...
}
Même dans une culture où le code de test n'est pas tellement écrit, les développeurs de maintenance doivent vérifier au moment du développement si les dépendances sont séparées et faciles à faire lors de l'instanciation. Ça ne me dérange pas. Je pense que c'est vers cette heure aujourd'hui.
Je l'ai écrit en p.s. Kotlin et j'ai pensé que ce serait bien pendant un moment parce que j'étais plein de Scala. Depuis que j'avais expérimenté Scala, le coût d'apprentissage était très faible (environ 2 heures de grammaire). Si j'ai une chance, je voudrais l'utiliser sérieusement dans mon entreprise.
Recommended Posts