miércoles, 18 de mayo de 2016

Mi experiencia en el mundo del testing automatizado

¡Hola! Hoy quería contarles mi experiencia en Testing Automatizado. ¿Cómo?, dirán los que me conocen… ¿pero vos no eras del mundo .Net? Sí, tienen razón, pero eso no quita que no pueda hacer otras cosas, en otras tecnologías o en otras ramas de la Ingeniería de Software, ¿no?

Desde hace unos meses me encuentro dándoles una mano a nuestros colegas de la Práctica QA de Baufest, coordinando y haciendo coaching a un equipo de automation para uno de nuestros clientes, tratando de aplicar mis conocimientos y mi experiencia como desarrollador/arquitecto .Net en pos de automatizar pruebas funcionales utilizando la dupla Selenium WebDriver + Ruby.

¿Qué es automatizar pruebas funcionales?

Básicamente podría resumirlo como “lograr que un conjunto de pruebas funcionales que se ejecutan manualmente, puedan ser ejecutadas de forma autónoma, sin necesidad de la intervención del tester funcional en dicho proceso”. Es decir, mediante el uso de una aplicación, o API (ya veremos la diferencia más adelante), poder generar una secuencia de pasos, que emulen las actividades del tester funcional, pero que sean ejecutadas de forma automática por una computadora. Interesante, ¿no? Imagínense la ventana que se nos abre por delante. El poder emular el comportamiento de una persona, a través de una secuencia de pasos pregrabados, o programados para tal fin. Ya sé, me dirán: pero con unit testing ya los desarrolladores creamos pruebas sobre un componente y eso emula el comportamiento de una persona realizando una prueba manual…Bueno, pero aquí la cuestión es diferente. Con unit testing nosotros como desarrolladores probamos un componente individual, una funcionalidad aislada, un pequeño eslabón en una cadena mucho más grande que implica probar todo un flujo completo, de una o más aplicaciones. 

El testing funcional, justamente implica probar una funcionalidad más compleja y, en la mayoría de los casos, implica trabajar, no con el método de una clase, sino con una pantalla de la aplicación. Y he aquí el desafío: automatizar las pruebas desde la propia interfaz de usuario, logrando ingresar datos en los componentes, haciendo clicks, validando qué mensajes o elementos se muestran, dónde, cómo, cuándo…

Y ni que hablar del testing de regresión, que implica probar todo un workflow funcional de punta a punta, pasando por varias pantallas y, ¿por qué no?, por distintas aplicaciones.

Bien, la buena noticia es que todo esto, y mucho más, puede ser automatizado gracias a un conjunto de aplicaciones y/o APIs que nos brindarán el soporte base para que podamos lograrlo.

¿Con qué herramientas contamos los qué hacemos automation?

Cuando me propusieron sumarme al equipo de Baufest, para realizar testing automatizado, no tenía bien en claro qué tanta de mi experiencia como desarrollador/arquitecto iba a poder aplicar. Por las dudas, les aclaro, nunca había hecho testing funcional en mi carrera profesional.

Si bien las personas que hacen automation tienen otros skills, más técnicos que los que poseen los testers funcionales, aún no sabía qué iba a poder aportar yo.

El primer choque con lo que ya estaba hecho me dejó sorprendido, sinceramente, encontré que había mucho más código del que yo esperaba ver, mejor dicho,¡había código! ¿Y esto a qué se debía? Investigando un poco encontré que, básicamente, para poder automatizar pruebas existen dos grandes enfoques:
  • Utilizar aplicaciones de record and play, que permiten grabar las pruebas funcionales directamente desde la aplicación. Una muy conocida es Selenium IDE, que permite grabar los pasos sobre el mismo browsery después ejecutarlos a demanda.
  • Utilizar un lenguaje de programación más una API y desarrollar código que emule el comportamiento del testing funcional, con los pasos representados a través de líneas de código.

Afortunadamente, el esquema de trabajo que se había encarado en el cliente era el segundo: utilizando Ruby como lenguaje, y Selenium WebDriver como API de trabajo.
Y lo de afortunadamente no lo digo sólo por un tema de que había código, sino porque este enfoque presenta algunas ventajas muy importantes:
  • Es mucho más potente, ya que permite generar código con mayor cantidad de lógica, validaciones, interacciones, etc. que las que ofrece por defecto la API que utilicemos.
  • Más allá de emplear la API para realizar las interacciones con la UI (del inglés: user interface), podemos aprovechar el contar con un lenguaje de programación para realizar otras tareas, como logging, reporting, exception handling, system calls, etc.
  • Permite generar pruebas funcionales más robustas y estables.
  • Facilita la generación y ejecución de pruebas de regresión (mediante la reutilización de pruebas funcionales individuales).
  • Si se diseña bien desde entrada permite, además, generar código reutilizable, escalable y mucho más mantenible.
  • Permite montar una arquitectura de pruebas automatizadas.

Un detalle no menor, es que las aplicaciones de automatización, como Selenium IDE, también suelen generar código con los pasos que se graban de la pantalla de la aplicación; pero es un código “desprolijo”, con muchas instrucciones que realmente no son necesarias y que complican la mantenibilidad. Además, no permiten reutilizar bloques de código generados previamente, ya que suelen ser independientes.


Este código es al que yo llamo scripting de pruebas automatizadas, porque justamente es eso, scripts independientes que prueban una funcionalidad dada, con poca o ninguna interacción con otros componentes, sin lineamientos de codificación (los scripts son generados por la aplicación usando los templates que posee predefinidos), y sin una arquitectura que brinde soporte por detrás.

El tester de automation

Antes les comentaba que el código que generan las aplicaciones como Selenium IDE es código “feo” desde el punto de vista del desarrollo. Pero si nosotros usamos un lenguaje y una API, y no tomamos ciertos recaudos, también vamos a estar generando scripts, y no una solución de pruebasEs por ello que, para poder automatizar pruebas, es necesario contar con conocimientos sobre desarrollo de aplicaciones, aprender un lenguaje de programación y tener ganas de desarrollar pruebas… Y es acá donde los dos mundos se cruzan, el mundo del testing, y el mundo del desarrollo. La persona que toma el rol de testing de automation, tiene que tener los dos perfiles:
  • El perfil del tester funcional, que le va a dar esa visión sobre lo que hay que probar, cómo armar los casos de pruebas, qué puntos de control (que básicamente son las verificaciones a nivel de UI y de base de datos) hay que validar, etc.
  • El perfil del desarrollador, para volcar esos casos de prueba y puntos de control a código, que sea ejecutable y, que, retorne de alguna manera ese “PASÓ / FALLÓ” que determina si una funcionalidad dada está bien desarrollada o no.


  

El desafío de trabajar con un lenguaje y una API: generar una buena arquitectura

El principal desafío de trabajar con una API (del inglés: Application Programming Interface) es trabajar bien con la misma. El segundo, es sacar provecho a esta modalidad de trabajo para poder hacer algo más que scripts. Ir más allá y generar lo que yo llamo una solución de pruebas automatizadas.

Volviendo a mi experiencia en el proyecto, cuando me sumé al equipo, lo primero que noté fue que había bastante funcionalidad de la aplicación con pruebas automatizadas asociadas, pero que estas pruebas funcionales eran scripts, casi individuales, con muy poca interacción con otros componentes. Fue ahí que surgió la idea, y la necesidad, de montar una arquitectura que diera soporte a los diferentes scripts que estábamos desarrollando para las pruebas automatizadas. Construir una buena arquitectura que nos permita mejorar el cómo estábamos trabajando hasta entonces. Y así pusimos manos a la obra para migrar los scripts, crear la arquitectura, pasar de trabajar con código procedural a trabajar con POO, hacer convivir ambos esquemas en la etapa de transición, extender la funcionalidad actual incorporando logging y reporting, mejorar el manejo de excepciones, subir los fuentes a un repositorio de SCM, estabilizar las pruebas actuales, integrarnos con otros sistemas externos, aumentar la cantidad de casos de pruebas ejecutados y, fundamentalmente, continuar con la construcción de nuevas pruebas funcionales automatizadas, según el plan acordado con el cliente.

Viendo la foto de cómo estamos hoy, a cómo estábamos hace 6 meses, es increíble el cambio que se ha logrado, no sólo en el código, sino en el equipo, en la dinámica de trabajo y en la robustez de la solución final. 

Me es imposible pensar en encarar un nuevo proyecto de automation, sin antes contar con una arquitectura y un esquema de trabajo que respalde las tareas  que se van a realizar. Y es en este punto en donde siento que pude aportar algo desde mi experiencia en el desarrollo de aplicaciones. Es que si bien estamos testeando, hay código de por medio, y ese código no difiere mucho del que se genera cuando desarrollamos aplicaciones, es código y está sujeto a los mismos “tires y aflojes”. El objetivo del mismo podrá ser distinto, pero créanme que con el tiempo he aprendido y validado que, si bien contar con una arquitectura no es garantía de éxito,  el no contar con ella es garantía de fracaso. Por eso, hablo de construir una buena arquitectura, que sea el respaldo del resto del código que se va a generar, ya sea que estemos hablando de aplicaciones o de pruebas funcionales automatizadas.

Algunas lecciones aprendidas

Luego de estos casi 6 meses trabajando en este proyecto, hemos logrado mejorar notablemente la calidad de nuestro trabajo: agilizar los tiempos de desarrollo de nuevas pruebas, aumentar la cantidad de casos de pruebas automatizados que se ejecutan, y muchas otras cosas más. Además, hemos ganado experiencia en la práctica y hemos aprendido algunas lecciones muy valiosas que quiero compartir con ustedes:

  •    Más allá de que estemos haciendo testing, además estamos generando código, y ese código tiene que estar alojado en un repositorio de SCM, tiene que tener un control de cambios y tiene que estar guiado por buenas prácticas de desarrollo.
  •    Antes que nada, tiene que definirse una arquitectura: conocer el sistema a testear, elegir el lenguaje, la API, seleccionar qué frameworks vamos a utilizar, con qué sistemas externos vamos a interactuar, qué reportes se deberían generar, entre otras tantas cosas más.
  •    Así como el desarrollo de una funcionalidad varía en el tiempo de acuerdo a cómo varían los casos de uso,el código de una prueba automatizada varía de acuerdo a cómo varían los casos de pruebas que lo definen (y estos varían a partir de los cambios en los casos de uso).
  •    Es mejor trabajar automatizando requerimientos estables, a fines de mitigar la cantidad de cambios que luego debamos realizarle a nuestros scripts.
  •    Es imposible encarar una estrategia de pruebas automatizadas exitosa sin una arquitectura ni un diseño base que sean los pilares de las pruebas.
  •      Utilizar una IDE, por más que en los cursos de automation digan que con un editor de textos alcanza y sobra para automatizar pruebas.

Como les contaba, fueron varios meses trabajando codo a codo con el resto del equipo: testers funcionales y de automation, la SDL y el cliente, con el objetivo de estabilizar y hacer valer todo el esfuerzo que se había realizado previamente, y el que se realizó durante este período. Gracias a todo ese esfuerzo, es que hoy estamos mucho más estables, hemos mejorado el nivel del servicio y vamos por más grandes y mejores desafíos en la práctica de testing automatizado. Por eso, aprovecho este espacio para agradecer y felicitar a todo el equipo que participó y se encuentra participando en este proyecto. Sin el aporte de cada uno de ellos, este trabajo hubiera sido imposible de encarar.

¡Hasta la próxima!

Ing. Ariel Martín Bensussán
.Net Practice Manager

miércoles, 4 de mayo de 2016

¿Cómo utilizar la librería Exchange Web Services?

Este artículo pretende brindar la información necesaria para el aprendizaje básico del uso de la librería EWS que brinda Outlook a través de su suite Office para automatizar tareas relacionadas con los mails.

Para empezar, debemos conocer qué significa EWS: Exchange Web Services. Para ponerlo simple, es el conjunto de funciones que actualmente usamos en Outlook pero disponibles como código fuente.

Por ejemplo, supongamos que abrimos el Outlook, creamos un nuevo e-mail y lo enviamos. En ese caso, identificamos tres acciones: abrir, crear, enviar. Esas acciones, también están disponibles mediante EWS. Podemos llamar a una función que nos permita conectarnos al servidor de Exchange para establecer la comunicación, tal como lo hace Outlook cuando lo abrimos, podemos llamar otra función que cree un objeto de tipo mail y a una última que envíe ese objeto al destinatario que indiquemos.

Esto nos introduce a un nuevo abanico de posibilidades, brindándonos el poder de automatizar tareas que hacemos a diario en nuestro trabajo. De esta forma, podemos incluir estas funciones en programas ya existentes para potenciarlos, e incluso podríamos crear una nueva versión customizada de un cliente de correos tal como lo es el Outlook. 

Ahora bien, una limitación importante es que EWS hace referencia al universo Exchange, o sea a los servidores de correo de Microsoft, por lo cual no podemos usarlo con cualquier cuenta que queramos como Gmail o Yahoo!. Esas cuentas usan otros protocolos de correo, por lo tanto estamos fuertemente relacionados a contar con un servidor Exchange disponible.


EWS vs EWS Api

Cuando hablamos sobre EWS, cabe destacar que estamos abarcando dos protocolos de comunicación bien distintos. EWS en sí, son Web Services que se ejecutan en el servidor de mail Exchange. Rápidamente, podemos decir que un Web Service es un protocolo de comunicación que expone el servidor. Significa que en el código fuente del servidor de mails hay funciones que podemos utilizar. Para hacerlo, debemos respetar un contrato, o más bien un formato para poder hablar con el servidor. El servidor nos expone el contrato para que sepamos cómo hablarle, si armamos un mensaje respetando ese formato y se lo enviamos al servidor, éste va a saber interpretarlo y obrar en consecuencia. Sus respuestas pueden ser insertar datos en una tabla interna, enviar un mail o incluso devolvernos una respuesta a nosotros, por ejemplo, informarnos la cantidad de mails que hay en el inbox.
La manera de armar el mensaje, es utilizando un formato tipo XML, lo cual puede ser engorroso y darnos más de un dolor de cabeza. Por esa razón, Microsoft desarrolló una API conocida formalmente como EWS Managed Api. Una librería que ya contiene el armado de esos contratos y nos permite con sólo hacer un llamado a una función y enviarle parámetros , armar el contrato y enviarlo al servidor. Por este motivo, debemos diferenciar EWS de EWS Api. 
Otra diferencia importante, es que no todos los contratos de EWS están contenidos en la EWS Api, por lo tanto hay mensajes específicos que sólo se pueden armar enviando un contrato XML. Sin embargo, con cada nueva versión de la librería se incluyen más funcionalidades.
En esta serie de artículos,  haremos referencia a EWS Api.

Manos a la obra 

Veamos cómo armar un programa simple. Su función será:


1. Conectarse al servidor.
2. Crear y enviarnos un mail a nosotros mismos.
3. Buscar el mail previamente enviado en el Inbox.
4. Si lo encontró, reenviarlo nuevamente pero con un mensaje agregado. Si no lo encontró, enviar un mail avisando de que no lo encontró.


Requisitos

Para empezar, debemos contar con las herramientas necesarias

  • Visual Studio: cualquier versión medianamente moderna de Visual Studio alcanzará.
  • EWS Managed Api: al momento de escribir este artículo la última versión disponible  es la 2.2. Puede ser descargada desde aquí: http://www.microsoft.com/en-us/download/details.aspx?id=42951 o bien podemos buscar "EWS Managed Api version" en cualquier buscador.

Su instalación es bien sencilla, ya que bajaremos un paquete de instalación que nos guiará a través de todo el proceso.

Creando el proyecto

Ahora sí entonces abriremos Visual Studio y crearemos un nuevo proyecto. Para nuestro
ejemplo, alcanzará con crear un proyecto de tipo consola y en C#, lo que significa que no tendrá formularios y utilizará una interfaz similar al MS-DOS.



Una vez creado el proyecto, debemos mover la DLL de EWS que descargamos y ubicarla dentro de los archivos del proyecto para un mejor ordenamiento: 



Volviendo a Visual Studio, nos encontramos con una clase llamada Program.cs:



Importando la DLL de EWS

El próximo paso es referenciar la DLL de EWS dentro del proyecto: 



Luego, hacemos clic en el botón “Browse”: 



Buscamos la ubicación dentro del proyecto en la cual dejamos las DDL de EWS Managed Api que descargamos: 





 Verificamos que las referencias hayan sido agregadas: 



Usando EWS

Habiendo hecho esto, volvemos a Program.cs para empezar a usarlas.Para ello las importamos escribiendo:

using Microsoft.Exchange.WebServices.Data;
using Microsoft.Exchange.WebServices.Auth;
using Microsoft.Exchange.WebServices;



Configurando la conexión


El próximo paso consiste en autentificarnos con el servidor de mails. Para ellos crearemos una nueva clase “DatosConexion” que contendrá la configuración. A fines educativos, lo haremos dentro del mismo Program.cs. 
Para una mejor organización, dichos valores deberían ir en un archivo de configuración app.config y el código que lee esa configuración y establece la conexión debería ir en un proyecto aparte.

class DatosConexion
    {
        public ExchangeVersion Version;
        public Uri AutodiscoverUrl;
        public string EmailAddress;
        public string Password;       
    }



Luego instanciaremos dicha clase y setearemos los valores:


DatosConexion conectar = new DatosConexion();
        conectar.Version = ExchangeVersion.Exchange2013;
        conectar.AutodiscoverUrl = new Uri("https://outlook.office365.com/EWS/
Exchange.asmx");
        conectar.EmailAddress = "tuMail@baufest.com";
        conectar.Password = "tuPassword";

Deberemos modificar los siguientes valores con los datos de nuestra dirección de mail.

conectar.EmailAddress = "tuMail@baufest.com";
conectar.Password = "tuPassword";

 



Conectándonos


Para conectarnos debemos utilizar un 'Listener', por ello debemos crear una clase Listener que utilice la interface iListener:

class TraceListener : ITraceListener
    {
        /*public void Trace(string traceType, string traceMessage)
        {
            //CreateXMLTextFile(traceType, traceMessage);
        }*/
        private void CreateXMLTextFile(string fileName, string traceContent)
        {
            try
            {
                if (!Directory.Exists(@"..\\TraceOutput"))
                    Directory.CreateDirectory(@"..\\TraceOutput");
                System.IO.File.WriteAllText(@"..\\TraceOutput\\" + fileName + DateTime.Now.Ticks + ".txt", traceContent);
            }
            catch (IOException ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }


Para utilizar las funciones de escritura en disco que utiliza esta clase, debemos importar la librería System.IO:

using System.IO;


  
El próximo paso consiste en consumir el TraceListener y crear una nueva NetworkCredential:

ExchangeService servicio = new ExchangeService(conectar.Version);
servicio.TraceListener = new TraceListener();
servicio.TraceFlags = TraceFlags.All;
servicio.TraceEnabled = true;
servicio.Credentials = new NetworkCredential(conectar.EmailAddress, conectar.Password);
servicio.Url = conectar.AutodiscoverUrl;



Deberemos importar una nueva librería para utilizar el método NetworkCredential:

using System.Net;

 

Enviando un mail


El siguiente paso será enviar un mail. Para ello, crearemos un nuevo método. Nuevamente, para fines prácticos, lo crearemos dentro de la clase Main:
public static void EnviarMail(ExchangeService unServicio, string unMensaje)
        {
            EmailMessage email = new EmailMessage(unServicio);
            email.Subject = "Soy un asunto";
            email.Body = @"<html><head></head><body><p>" + unMensaje + "<br></p></body>
</html>";
            email.Body.BodyType = BodyType.HTML;
            email.ToRecipients.Add("tuMail@baufest.com");
            email.SendAndSaveCopy();
        }

Deberemos cambiar la línea a continuación con nuestra dirección de mail:

email.ToRecipients.Add("tuMail@baufest.com");



Consumiremos dicho método de la siguiente manera:

EnviarMail(servicio, "Soy un e-mail.");


 Buscando el mail que enviamos previamente
A continuación, buscaremos en el inbox el mail que acabamos de enviar. Esto puede llegar a fallar (lo trataremos más adelante) debido a la demora que se presenta entre que enviamos el mail y que lo recibimos.
Primero prepararemos una vista, que es el conjunto de items que devolverá la búsqueda.Por default lo limitamos a 100 items:

ItemView vista = new ItemView(100);
            vista.PropertySet = new PropertySet(BasePropertySet.IdOnly, ItemSchema.Subject
ItemSchema.ItemClass, EmailMessageSchema.Sender);


Luego crearemos los filtros de búsqueda:
List<SearchFilter> searchFilterSubjectCollection = new List<SearchFilter>();
            searchFilterSubjectCollection.Add(new SearchFilter.ContainsSubstring
(EmailMessageSchema.Subject, "Soy un asunto"));
            SearchFilter filtroDeBusqueda = new SearchFilter.SearchFilterCollection
(LogicalOperator.Or, searchFilterSubjectCollection.ToArray());



Finalmente, ejecutamos nuestra búsqueda y el resultado queda guardado en una colección:
FindItemsResults<Item> resultadoDeBusqueda = servicio.FindItems(WellKnownFolderName.Inbox,
 filtroDeBusqueda, vista);



Ahora verificamos el resultado. Si lo encontró, entonces nos reenviamos el mail y agregamos el texto "encontré el mail.". En caso contrario, se envía un mail avisando que no pudo encontrar el mail (esto puede ser porque aún no ha llegado el mail que enviamos al principio). En ese caso, esperaremos unos minutos y volveremos a ejecutar el programa.

            if (resultadoDeBusqueda.TotalCount > 0) {
                EmailMessage emailEncontrado = (EmailMessage) resultadoDeBusqueda.First();
                emailEncontrado.Reply("Encontré el mail", true);
            }
            else
            {
                EnviarMail(servicio, "No encontré el mail.");
            }



Código fuente final


Este es el resultado final de todo lo mencionado:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Exchange.WebServices.Data;
using Microsoft.Exchange.WebServices.Auth;
using Microsoft.Exchange.WebServices;
using System.IO;
using System.Net;
namespace EjemploEWS
{
    class Program
    {
        static void Main(string[] args)
        {
            DatosConexion conectar = new DatosConexion();
            conectar.Version = ExchangeVersion.Exchange2013;
            conectar.AutodiscoverUrl = new Uri("https://outlook.office365.com/EWS/Exchange.asmx");
            conectar.EmailAddress = "tuMail@baufest.com";
            conectar.Password = "tuPassword";
            ExchangeService servicio = new ExchangeService(conectar.Version);
            servicio.TraceListener = new TraceListener();
            servicio.TraceFlags = TraceFlags.All;
            servicio.TraceEnabled = true;
            servicio.Credentials = new NetworkCredential(conectar.EmailAddress, conectar.Password);
            servicio.Url = conectar.AutodiscoverUrl;
 
            EnviarMail(servicio, "Soy un e-mail.");
            ItemView vista = new ItemView(100);
            vista.PropertySet = new PropertySet(BasePropertySet.IdOnly, ItemSchema.Subject,
ItemSchema.ItemClass, EmailMessageSchema.Sender);
            List<SearchFilter> searchFilterSubjectCollection = new List<SearchFilter>();
            searchFilterSubjectCollection.Add(new SearchFilter.ContainsSubstring
(EmailMessageSchema.Subject, "Soy un asunto"));
            SearchFilter filtroDeBusqueda = new SearchFilter.SearchFilterCollection
(LogicalOperator.Or, searchFilterSubjectCollection.ToArray());
             FindItemsResults<Item> resultadoDeBusqueda = servicio.FindItems
(WellKnownFolderName.Inbox, filtroDeBusqueda, vista);
            if (resultadoDeBusqueda.TotalCount > 0) {
                EmailMessage emailEncontrado = (EmailMessage) resultadoDeBusqueda.First();
                emailEncontrado.Reply("Encontré el mail", true);
            }
            else
            {
                EnviarMail(servicio, "No encontré el mail.");
            }
         }
        public static void EnviarMail(ExchangeService unServicio, string unMensaje)
        {
            EmailMessage email = new EmailMessage(unServicio);
email.Subject = "Soy un asunto";
            email.Body = @"<html><head></head><body><p>" + unMensaje + "<br></p></body>
</html>";
            email.Body.BodyType = BodyType.HTML;
            email.ToRecipients.Add("tuMail@baufest.com");
            email.SendAndSaveCopy();
        }
    }
    class DatosConexion
    {
        public ExchangeVersion Version;
        public Uri AutodiscoverUrl;
        public string EmailAddress;
        public string Password;       
    }
class TraceListener : ITraceListener
    {
        public void Trace(string traceType, string traceMessage)
        {
            //CreateXMLTextFile(traceType, traceMessage);
        }
  private void CreateXMLTextFile(string fileName, string traceContent)
        {
            try
            {
                if (!Directory.Exists(@"..\\TraceOutput"))
                    Directory.CreateDirectory(@"..\\TraceOutput");
                System.IO.File.WriteAllText(@"..\\TraceOutput\\" + fileName + DateTime.Now.Ticks +
".txt", traceContent);
            }
            catch (IOException ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }
}

Links:
Más información sobre las diferencias entre EWS y EWS Managed Api: https://msdn.microsoft.com/en-us/library/office/dd633678(v=exchg.80).aspx


Autor:


 







Julián Haeberli
Baufest Technical Leader