Estoy usando un marco que basó su ORM en el patrón Active Record . Cada tabla que tengo en mi base de datos está vinculada a un modelo en mi código.
Quiero hacer una prueba unitaria de estos modelos, así que comencé extrayendo cada llamada save() y update() de los modelos, de modo que los cambios se realicen solo en los objetos y solo se mantengan una vez que se necesiten.
Sin embargo, no sé cómo puedo aplicar esta estrategia en este caso. Tengo un modelo de Chat , del que forma parte un User , y el User puede agregar una ChatNote de chat al Chat .
Aquí está la implementación actual:
// User.php public function addChatNote($chatNoteContent, Chat $chat) { $chatNote = new ChatNote(); $chatNote->content = $chatNoteContent; $chatNote->save(); $chat->note()->associate($chatNote); $chat->save(); } Ahora, a mis ojos, hay toneladas de problemas aquí, pero las llamadas save() realmente están arruinando mi oportunidad de hacer una prueba unitaria adecuada aquí.
Pensé en crear ChatNote antes de usar ese método, pero ¿qué clase debería ser responsable de crearlo? También podría guardarlo en otro lugar y guardar el Chat en otro lugar, pero ¿el User es realmente responsable de otra cosa que no sea asociar ChatNote al Chat ? ¿Y dónde se debe hacer save() ?
De hecho, algunos probadores clásicos de xunit también argumentan que cualquier colaboración con recursos externos, como una base de datos o un sistema de archivos, debería usar dobles. Esto se debe en parte al riesgo de no determinismo, en parte a la velocidad. Si bien creo que esta es una guía útil, no considero el uso de dobles para recursos externos como una regla absoluta. Si hablar con el recurso es lo suficientemente estable y rápido para usted, entonces no hay razón para no hacerlo en sus pruebas unitarias.
( Martin Fowler )
Parece que estás usando Laravel. El marco proporciona algunas formas sencillas de integrar la base de datos en sus pruebas .
Otra práctica común es usar una base de datos separada en memoria para las pruebas. Incluso puede crear el esquema desde cero (ejecutando todas las migraciones) antes de cada prueba. Simplemente agregue esto a su phpunit.xml :
<php> <env name="DB_CONNECTION" value="sqlite"/> <env name="DB_DATABASE" value=":memory:"/> </php>Creo que deberías tener un ChatNoteFactory que devolvería un ChatNote, pasando un contenido.
Como dijo @user3807702, puede emplear el patrón AbstractFactory o el Factory method para manejar la prueba unitaria de la aplicación. P.ej
public function addChatNote($chatNoteContent, Chat $chat) { $chatNote = $this->chatNoteFactory->newInstance(); $chatNote->content = $chatNoteContent; $chatNote->save(); $chat->note()->associate($chatNote); $chat->save(); } Esto lo ayudará a crear un simulacro de ChatNote que no involucrará ninguna comunicación con la base de datos durante la prueba.
$chatNote = $this->getMockBuilder(ChatNote::class) ->disableConstructor() ->setMethods(['save']) ->getMock(); $chatNoteFactoryMock = $this ->getMockBuilder(chatNoteFactoryMock::class) ->getMock(); $chatNoteFactoryMock ->method('newInstance') ->willReturn($chatNote); //Pass the chat note factory mock to the User Lo mismo que hará con el objeto Chat : pase su simulación a addChatNote . Por lo tanto, obtendrá pruebas unitarias puras sin comunicación DB.
Todavía debería decir que save objetos dentro del User podría considerarse una violación del principio de Separación de preocupaciones . El User debe molestarse en almacenar Chat y ChatNote en la base de datos. Sería bueno poner los saves afuera: puede hacerlo de manera explícita o usando objetos proxy, marcos AOP, etc.