2008-09-18 11 views
19

Busco una herramienta que puede tomar una prueba de unidad, comoGenerando clases automáticamente a partir de pruebas unitarias?

IPerson p = new Person(); 
p.Name = "Sklivvz"; 
Assert.AreEqual("Sklivvz", p.Name); 

y generar, de forma automática, la clase stub correspondiente y la interfaz

interface IPerson   // inferred from IPerson p = new Person(); 
{ 
    string Name 
    { 
     get;    // inferred from Assert.AreEqual("Sklivvz", p.Name); 
     set;    // inferred from p.Name = "Sklivvz"; 
    } 
} 

class Person: IPerson  // inferred from IPerson p = new Person(); 
{ 
    private string name; // inferred from p.Name = "Sklivvz"; 

    public string Name // inferred from p.Name = "Sklivvz"; 
    { 
     get 
     { 
      return name; // inferred from Assert.AreEqual("Sklivvz", p.Name); 
     } 
     set 
     { 
      name = value; // inferred from p.Name = "Sklivvz"; 
     } 
    } 

    public Person()  // inferred from IPerson p = new Person(); 
    { 
    } 
} 

Sé ReSharper y Visual Studio hacen algunos de estos, pero necesito una herramienta completa - línea de comandos o lo que sea - que deduce automáticamente lo que se necesita hacer. Si no hay tal herramienta, ¿cómo la escribiría (por ejemplo, extendiendo ReSharper, desde cero, utilizando qué bibliotecas)?

+0

Puede hacerlo con una [plantilla T4.] (Http://msdn.microsoft.com/en-us/library/bb126445.aspx) No tengo una muestra útil, pero podría intentar escribir uno, si estás interesado. Pude ver cómo esto podría ser realmente útil en el desarrollo Test-First (TDD); el truco es compilar su código mientras escribe pruebas para métodos que aún no existen. El otro problema, por supuesto, es que la Plantilla T4 probablemente necesite ser más inteligente que lo que describió, y dudo que haya proporcionado una especificación completa en su ejemplo. –

+0

@RobertHarvey, estaba discutiendo esto con un compañero de trabajo, y él tiene la impresión de que podría haber demasiada inferencia por hacer. Estaría feliz con algo que funciona de una manera significativamente más inteligente que la locura actual del "clic derecho" :-) – Sklivvz

Respuesta

4

Lo que parecen necesitar es un analizador para su lenguaje (Java) y un nombre y tipo resolver. ("Generador de tablas de símbolos").

Después de analizar el texto de origen, un compilador generalmente tiene un nombre de resolución, que intenta registrar la definición de nombres y sus tipos correspondientes, y un verificador de tipos, que verifica que cada expresión tenga un tipo válido.

Normalmente el nombre/tipo resolver se queja cuando no puede encontrar una definición. Lo que quiere hacer es encontrar el elemento "indefinido" que está causando el problema e inferir un tipo para él.

Para

IPerson p = new Person(); 

la resolución de nombres sabe que "Persona" y "IPerson" no están definidos. Si se tratara de

Foo p = new Bar(); 

no habría ninguna pista que quería una interfaz, sólo que Foo es una especie de matriz abstracta de barra (por ejemplo, una clase o una interfaz). Por lo tanto, la herramienta debe saber qué decisión es la suya ("siempre que encuentre una construcción así, suponga que Foo es una interfaz ..."). Podría usar una heurística: IFoo y Foo significan que IFoo debería ser una interfaz, y en algún lugar alguien tiene que definir a Foo como una clase que se da cuenta de esa interfaz. Una vez que la herramienta ha tomado esta decisión, tendría que actualizar sus tablas de símbolos para que pueda pasar a otras declaraciones:

Para

p.Name = "Sklivvz"; 

dado que p debe ser una interfaz (por el inferencia anterior), entonces Name debe ser un miembro del campo, y parece que su tipo es String de la asignación.

Con eso, la declaración:

Assert.AreEqual("Sklivvz", p.Name); 

nombres y tipos resuelven sin más problema.

El contenido de las entidades IFoo y Foo depende de usted; no tenías que usar get y set pero ese es tu gusto personal.

Esto no funcionará tan bien cuando tiene varias entidades en la misma declaración:

x = p.a + p.b ; 

Sabemos que ayb son los dominios que podrían, pero no se puede adivinar qué tipo numérico si es que son numérico, o si son cadenas (esto es legal para cadenas en Java, no sé sobre C#). Para C++ ni siquiera sabes lo que significa "+"; podría ser un operador en la clase Bar. Entonces, lo que tiene que hacer es recopilar restricciones, por ejemplo, "a es un número indefinido o una cadena", etc. y a medida que la herramienta recolecta evidencia, reduce el conjunto de posibles restricciones. (Esto funciona como esos problemas planteados: "Joe tiene siete hijos. Jeff es más alto que Sam. Harry no puede esconderse detrás de Sam. ... ¿quién es el gemelo de Jeff?" Donde tiene que reunir la evidencia y eliminar las imposibilidades). También debe preocuparse por el caso en el que termina con una contradicción.

Puede descartar el caso p.a + p.b, pero no puede escribir las pruebas de su unidad con total impunidad. Hay solucionadores de restricciones estándar por ahí si quieres impunidad. (Que concepto)

OK, tenemos las ideas, ahora, ¿se puede hacer esto de una manera práctica?

La primera parte de este requiere un analizador y un nombre y tipo flexible resolver. Necesita un solucionador de restricciones o al menos una operación de "valores de valor definidos para un valor indefinido" (solucionador de restricciones triviales).

Nuestra DMS Software Reengineering Toolkit con su Java Front End probablemente podría hacer esto. DMS es una herramienta de creación de herramientas para personas que desean crear herramientas que procesen idiomas de forma arbitraria. (Piense en "calcular con fragmentos de programa en lugar de números").

DMS ofrece máquinas de análisis de uso general, y pueden construir un árbol por cualquier extremo delantero se le da (por ejemplo, Java, y hay una # delante extremo C). La razón por la que elegí Java es que nuestra interfaz Java tiene todo ese nombre y la maquinaria de resolución de tipo, y se proporciona en forma de fuente para que pueda ser doblada. Si te mantienes en el solucionador de restricciones triviales, probablemente puedas doblar la resolución del nombre de Java para descubrir los tipos. DMS le permitirá ensamblar árboles que corresponden a fragmentos de código, y combinarlos en otros más grandes; a medida que su herramienta recopile datos para la tabla de símbolos, podría construir los árboles primitivos.

En algún lugar, debe decidir que ha terminado. ¿Cuántas pruebas unitarias debe ver la herramienta antes de conocer toda la interfaz? (¿Supongo que se come todas las que le das?). Una vez completo, ensambla los fragmentos para los distintos miembros y crea un AST para una interfaz; DMS puede usar su impresora bonita para convertir ese AST nuevamente en código fuente como lo ha demostrado.

puedo sugerir Java aquí porque nuestro extremo frontal Java tiene nombre y tipo de resolución. Nuestra parte frontal C# no. Esta es una "mera" cuestión de ambición; alguien tiene que escribir uno, pero eso es bastante trabajo (al menos fue para Java y no puedo imaginar que C# sea realmente diferente).

Pero la idea funciona bien en principio usando DMS.

usted puede hacer esto con un poco de otras infraestructuras que daba acceso a un analizador y un nombre flexible y tipo de resolución. Eso podría no ser tan fácil de obtener para C#; Sospecho que MS puede darte un analizador y acceso a nombre y tipo de resolución, pero no hay forma de cambiar eso. Quizás Mono es la respuesta?

usted todavía necesita un fue generar fragmentos de código y ensamblarlos. Puede intentar hacer esto pirateando cuerdas; mi (larga) experiencia con el pegado de bits del programa es que si lo haces con cadenas, eventualmente lo arruinas. Realmente quieres piezas que representen fragmentos de código de tipo conocido, que solo se puedan combinar de forma que permita la gramática; DMS lo hace así sin ensuciar.

-2

Me parece que cada vez que necesito una herramienta de generación de código como esta, probablemente estoy escribiendo código que podría hacerse un poco más genérico, así que solo necesito escribirlo una vez. En su ejemplo, esos getters y setters no parecen estar agregando ningún valor al código; de hecho, es solo afirmar que el mecanismo getter/setter en C# funciona.

Me abstendría de escribir (o incluso utilizar) una herramienta de este tipo antes de entender cuáles son las motivaciones para escribir este tipo de pruebas.

Por cierto, es posible que desee echar un vistazo a NBehave?

1

Si va a escribir su propia implementación, definitivamente le sugiero que eche un vistazo a los motores de plantilla NVelocity (C#) o Velocity (Java).

He usado esto en un generador de código antes y he descubierto que hacen el trabajo mucho más fácil.

-1

barcos de Visual Studio con algunas características que pueden ser útiles para usted aquí:

Generar método del trozo. Cuando escribe una llamada a un método que no existe, obtendrá una pequeña etiqueta inteligente en el nombre del método, que puede usar para generar un apéndice del método en función de los parámetros que está pasando.

Si usted es una persona teclado (soy), luego a la derecha después de escribir el paréntesis de cierre, que puede hacer:

  • Ctrl. (para abrir la etiqueta inteligente)
  • ENTER (para generar el talón)
  • F12 (pase a la definición, que le llevará al nuevo método)

sólo aparece en la etiqueta inteligente si el IDE piensa que no hay un método que coincida. Si desea generar cuando la etiqueta inteligente no está activa, puede ir al Editar-> Intellisense-> Generar trozo de método.

Fragmentos. Plantillas de código pequeño que facilitan la generación de bits de código común. Algunos son simples (prueba "si [TAB] [TAB]").Algunos son complejos ('switch' generará casos para una enumeración). También puedes escribir el tuyo. Para su caso, pruebe "clase" y "prop".

Consulte también "How to change “Generate Method Stub” to throw NotImplementedException in VS?" para obtener fragmentos de información en el contexto de GMS.

autoprops. Recuerde que las propiedades pueden ser mucho más simple:

public string Name { get; set; } 

Crear clase. En el Explorador de soluciones, haga clic en el nombre del proyecto o una subcarpeta, seleccione Agregar-> Clase. Escriba el nombre de su nueva clase. Presione ENTER. Obtendrá una declaración de clase en el espacio de nombres correcto, etc.

Implemente la interfaz. Cuando desee que una clase implemente una interfaz, escriba la parte del nombre de la interfaz, active la etiqueta inteligente y seleccione cualquiera de las opciones para generar stubs para los miembros de la interfaz.

Estas no son exactamente la solución 100% automatizada que está buscando, pero creo que es una buena mitigación.

+0

Gracias, ya uso esas herramientas, pero lo que me interesa es mucho más sofisticado, por ejemplo, debe inferir de la asignación y la afirmación de que la propiedad se lee escribir – Sklivvz

0

Me gusta CodeRush de DevExpress. Tienen un gran motor personalizable de plantillas. Y lo mejor para mí es que no hay cuadros de diálogo. También tienen funcionalidad para crear métodos e interfaces y clases desde la interfaz que no existe.

3

Es sorprendente cómo nadie realmente dio nada de lo que estabas preguntando.

No sé la respuesta, pero voy a dar mi opinión al respecto.

Si tuviera que intentar escribir algo como esto, probablemente vería un plugin de resharper. La razón por la que digo eso es porque, como dijiste, el reafilador puede hacerlo, pero en pasos individuales. Así que escribía algo que iba línea por línea y aplicaba los métodos apropiados de creación de resharper encadenados.

Ahora, de ninguna manera, ni siquiera sé cómo hacer esto, ya que nunca he construido algo para resharper, pero eso es lo que trataría de hacer. Tiene sentido lógico que se pueda hacer.

Y si escribe algún código, POR FAVOR publíquelo, ya que podría encontrarlo también útil, pudiendo generar todo el esqueleto en un solo paso. Muy útil.

1

Es posible, al menos en teoría. Lo que haría es usar algo como csparser para analizar la prueba unitaria (no puedes compilarla, desafortunadamente) y luego tomarla desde allí. El único problema que puedo ver es que lo que estás haciendo está mal en términos de metodología: tiene más sentido generar pruebas unitarias a partir de clases de entidad (de hecho, Visual Studio hace precisamente esto) que hacerlo al revés.

+0

No es una metodología incorrecta, solo diferente. Toma TDD como la base del desarrollo. – MrJames

+0

@DmitriNesteruk Acabo de leer algunos de los documentos de desarrollo de tu complemento resharper ... ¿Cuán difícil crees que sería crear un complemento para hacer esto? Como dice el OP, ya hace la mayor parte de lo que necesita, solo necesita que algunas de las características existentes terminen en una sola acción. – Robbie

+0

@Robbie ReSharper ya hace mucho de esto a través de 'crear a partir del uso'. Si lo necesita al por mayor, puede automatizar las acciones R # existentes para realizar muchos cambios a la vez, y * esta * parte puede ser un poco desafiante. –

0

Trate de buscar en el Pex, un proyecto de Microsoft en la unidad de pruebas, que todavía está bajo investigación

research.microsoft.com/en-us/projects/Pex/

0

Creo que lo que busca es un kit de herramientas de pelusa (https://en.wikipedia.org/wiki/Fuzz_testing).

Al duro que nunca se utiliza, es posible dar Randoop.NET una oportunidad para generar '' pruebas unitarias http://randoop.codeplex.com/

1

creo que una solución real a este problema sería un programa de análisis muy especializado. Como eso no es tan fácil de hacer, tengo una idea más barata. Por desgracia, habría que cambiar la forma en que escribe sus pruebas (es decir, sólo la creación del objeto):

dynamic p = someFactory.Create("MyNamespace.Person"); 
p.Name = "Sklivvz"; 
Assert.AreEqual("Sklivvz", p.Name); 

un objeto de fábrica se utilizaría. Si puede encontrar el objeto nombrado, lo creará y lo devolverá (esta es la ejecución de prueba normal). Si no lo encuentra, creará un proxy de grabación (un DynamicObject) que registrará todas las llamadas y al final (tal vez al derribarlo) podría emitir archivos de clase (tal vez basados ​​en algunas plantillas) que reflejen lo que "vio " siendo llamado.

Algunas desventajas que veo:

  • necesidad de ejecutar el código en "dos" modos, lo cual es molesto.
  • Para que el proxy pueda "ver" y grabar llamadas, se deben ejecutar; de modo que el código en un bloque catch, por ejemplo, tiene que ejecutarse.
  • Tienes que cambiar la forma en que creas tu objeto bajo prueba.
  • Tienes que usar dynamic; perderá seguridad en tiempo de compilación en las siguientes ejecuciones y tendrá un impacto en el rendimiento.

La única ventaja que veo es que es mucho más barato de crear que un programa de análisis especializado.

Cuestiones relacionadas