zoukankan      html  css  js  c++  java
  • app hybrid

    package com.note.testcases;
    /**
    *
    * The MIT License (MIT)
    *
    * Copyright (c) 2016 Alejandro Gómez Morón
    * 
    * Permission is hereby granted, free of charge, to any person obtaining a copy
    * of this software and associated documentation files (the "Software"), to deal
    * in the Software without restriction, including without limitation the rights
    * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    * copies of the Software, and to permit persons to whom the Software is
    * furnished to do so, subject to the following conditions:
    * 
    * The above copyright notice and this permission notice shall be included in all
    * copies or substantial portions of the Software.
    * 
    * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    * SOFTWARE.
    */
    
    import java.io.File;
    import java.net.URL;
    import java.util.ArrayList;
    import java.util.Date;
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    import java.util.concurrent.TimeUnit;
    
    import org.apache.commons.lang3.StringUtils;
    import org.apache.log4j.Logger;
    import org.openqa.selenium.By;
    import org.openqa.selenium.Capabilities;
    import org.openqa.selenium.JavascriptExecutor;
    import org.openqa.selenium.Platform;
    import org.openqa.selenium.ScreenOrientation;
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.WebDriver.TargetLocator;
    import org.openqa.selenium.WebElement;
    import org.openqa.selenium.html5.Location;
    import org.openqa.selenium.remote.DesiredCapabilities;
    import org.openqa.selenium.remote.ExecuteMethod;
    import org.openqa.selenium.remote.RemoteWebDriver;
    
    import com.google.gson.JsonObject;
    
    import io.appium.java_client.AppiumDriver;
    import io.appium.java_client.MobileElement;
    import io.appium.java_client.MultiTouchAction;
    import io.appium.java_client.TouchAction;
    import io.appium.java_client.android.AndroidDriver;
    import io.appium.java_client.ios.IOSDriver;
    import io.appium.java_client.remote.MobileCapabilityType;
    
    /**
    * Appium handler driver to work with any appium implementation iOS/Android and working with
    * the app independent of the app (native or hybrid).
    * 
    * @author Alejandro Gomez <agommor@gmail.com>
    * @author Ivan Gomez de Leon <igomez@emergya.com>
    *
    */
    public class DevTest {
    
       /**
        * Log instance.
        */
       private final static Logger LOGGER = Logger.getLogger(DevTest.class);
    
       /**
        * Key to be used in the {@link DesiredCapabilities} checking.
        */
       private static String PLATFORM_TYPE_KEY = "platformName";
    
       /**
        * Key to be used in the {@link DesiredCapabilities} checking.
        */
       private static String APP_KEY = "app";
    
       /**
        * Key to be used in the {@link DesiredCapabilities} checking.
        */
       private static String APP_HYBRID = "appHybrid";
    
       /**
        * Parameter to have always the main window.
        */
       private String mainWindow;
    
       /**
        * Involved instance (decorator pattern).
        */
       private AppiumDriver<MobileElement> driver;
    
       /**
        * Flag to know if we're testing an hybrid app.
        */
       private boolean isAnHybridApp;
    
       /**
        * Builder method to create {@link DevTest} instances.
        * @param remoteAddress to be used.
        * @param desiredCapabilities to be used.
        * @return an {@link DevTest} instance with the custom implementation.
        */
       public static DevTest buildInstance(URL remoteAddress, DesiredCapabilities desiredCapabilities) {
           DevTest instance = null;
    
           // getting app path (if it's exists)
           Object appCapability = desiredCapabilities.getCapability(APP_KEY);
           if (appCapability != null && appCapability instanceof String) {
               String appPath = (String) appCapability;
               File file = new File(appPath);
               if (file.exists()) {
                   desiredCapabilities.setCapability(APP_KEY, file.getAbsolutePath());
               } else {
                   LOGGER.error("The app was defined but it cannot be found in " + appPath);
               }
           }
    
           AppiumDriver<MobileElement> driver = null;
    
           // building the instance
           if (isIOS(desiredCapabilities)) {
               driver = new IOSDriver<MobileElement>(remoteAddress, desiredCapabilities);
           } else if (isAndroid(desiredCapabilities)) {
               driver = new AndroidDriver<MobileElement>(remoteAddress, desiredCapabilities);
           } else {
               // TODO: work on it. Nowadays just iOS and android are supported by this handler.
               driver = new AndroidDriver<MobileElement>(remoteAddress, desiredCapabilities);
           }
    
           // implicit wait for slow devices
           driver.manage().timeouts().implicitlyWait(35, TimeUnit.SECONDS);
           Boolean isHybrid = false;
           Object appHybrid = desiredCapabilities.getCapability(APP_HYBRID);
           desiredCapabilities.setCapability(MobileCapabilityType.TAKES_SCREENSHOT, "true");
           if (appHybrid != null && appHybrid instanceof Boolean) {
               isHybrid = (Boolean) appHybrid;
               if (isHybrid) {
                   // if the app is hybrid, we have to wait until the WEBVIEW context handler exists
                   driver = switchToWebViewContext(driver);
               }
           }
           // now the driver is configured, we create the wrapper
           instance = new DevTest(driver, isHybrid);
           return instance;
       }
    
       /**
        * This method switches to webview context (for hybrid apps).
        * @param driver to be switched to.
        * @param 
        * @return modified driver instance.
        */
       private static AppiumDriver<MobileElement> switchToWebViewContext(AppiumDriver<MobileElement> driver) {
           long start = new Date().getTime();
           long end = new Date().getTime();
           long limit = 15; // waiting no more than 15 seconds to switch to a WEBVIEW context
           boolean switched = false;
           int maxRetries = 5;
           // for HYBRID APPS, switching the context
           do {
               sleepFor(1);
               Set<String> contextHandles = driver.getContextHandles();
               for (String context : contextHandles) {
                   if (context.contains("WEBVIEW")) {
                       // the context change needs some extra time
                       int retries = 0;
                       do {
                           sleepFor(5);
                           try {
                               driver.context(context);
                           } catch (Exception ex) {
                               LOGGER.warn("An error occurred switching the context. Trying again...");
                           }
                           retries++;
                       } while (!driver.getContext().contains("WEBVIEW") && retries < maxRetries);
                       switched = true;
                   }
               }
               end = new Date().getTime();
           } while (!switched && (start + (limit * 1000)) > end);
    
           if (!switched) {
               LOGGER.error("After waiting for " + limit
                       + " seconds, the driver couldn't switched to the WEBVIEW context, so the test of the hybrid application will failed!");
           }
           return driver;
       }
    
       /**
        * Method to know if the driver is ready to test hybrid / native apps.
        * @return true if the driver is well configured or false otherwise.
        */
       public boolean isDriverReadyToTest() {
           boolean ready = false;
           if (this.driver != null) {
               if (this.isAnHybridApp) {
                   if (this.getContext().contains("WEBVIEW")) {
                       ready = true;
                   }
               } else {
                   if (!this.getContext().contains("WEBVIEW")) {
                       ready = true;
                   }
               }
           }
           return ready;
       }
    
       /**
        * Private constructor to avoid instances creation without using the buildInstance method.
        * @param driver an {@link AppiumDriver} instance.
        * @param isHybrid flag to know if it's an hybrid app.
        */
       private DevTest(AppiumDriver<MobileElement> driver, Boolean isHybridApp) {
           this.driver = driver;
           this.isAnHybridApp = isHybridApp;
           this.mainWindow = this.driver.getWindowHandle();
       }
    
       /**
        * It switches to the main window if it's an hybrid app.
        */
       public void switchToMainWindow() {
           if (this.isAnHybridApp && StringUtils.isNotBlank(this.mainWindow)
                   && !this.driver.getWindowHandle().equals(this.mainWindow)) {
               this.driver.switchTo().window(this.mainWindow);
           }
       }
    
       /**
        * It checks if it's an iOS platform.
        * @param desiredCapabilities to check if the SO is iOS.
        * @return true if it's an iOS testing.
        */
       private static boolean isIOS(Capabilities desiredCapabilities) {
           return is(desiredCapabilities, "ios");
       }
    
       /**
        * It checks if it's an Android platform.
        * @param desiredCapabilities to check if the SO is iOS.
        * @return true if it's an iOS testing.
        */
       private static boolean isAndroid(Capabilities desiredCapabilities) {
           return is(desiredCapabilities, Platform.ANDROID.name());
       }
    
       /**
        * It checks if it's an iOS platform.
        * @param desiredCapabilities to check if the SO is iOS.
        * @param type to check.
        * @return true if it's an iOS testing.
        */
       private static boolean is(Capabilities desiredCapabilities, String type) {
           boolean is = false;
           if (desiredCapabilities != null) {
               Object capability = desiredCapabilities.getCapability(PLATFORM_TYPE_KEY);
               if (capability != null && capability instanceof String) {
                   if (type != null && type.equalsIgnoreCase(((String) capability))) {
                       is = true;
                   }
               }
           }
           return is;
       }
    
       /**
        * This method waits for the {@link MobileElement} described by the {@By} selector with a timeout of seconds.
        * @param selector to get the element.
        * @param seconds to wait for (timeout).
        * @param message to send to the log if something happens.
        */
       public void waitFor(By selector, long seconds, String message) {
           LOGGER.info("Waiting for " + selector.toString());
           long start = new Date().getTime();
           long end = start + (seconds * 1000);
           long now = new Date().getTime();
           MobileElement element = null;
           do {
               element = this.findElement(selector);
               now = new Date().getTime();
           } while (element == null && now <= end);
    
           if (element == null) {
               if (message != null && "".equals(message.trim())) {
                   LOGGER.error("After waiting " + seconds + " seconds for the element " + selector.toString()
                           + ", the element is missing!. Custom message: " + message);
               } else {
                   LOGGER.error("After waiting " + seconds + " seconds for the element " + selector.toString()
                           + ", the element is missing!");
               }
           }
       }
    
       /**
        * This method waits for the {@link MobileElement} described by the {@By} selector with a timeout of seconds.
        * @param selector to get the element.
        * @param seconds to wait for (timeout).
        */
       public void waitFor(By selector, long seconds) {
           this.waitFor(selector, seconds, null);
       }
    
       /**
        * This method waits for the {@link MobileElement} until it's visible described by the {@By} selector with a timeout of seconds.
        * @param selector to get the element.
        * @param seconds to wait for (timeout).
        * @param message to send to the log if something happens.
        */
       public void waitUntilVisible(By selector, long seconds, String message) {
           LOGGER.info("Waiting for " + selector.toString());
           this.waitFor(selector, seconds, message);
           MobileElement element = this.findElement(selector);
           if (element != null && !element.isDisplayed()) {
               LOGGER.error("After waiting " + seconds + " seconds for the element " + selector.toString()
                       + " exists in the DOM but is not displayed.");
           }
       }
    
       /**
        * It sleeps the driver for n seconds.
        * @param seconds to be slept.
        */
       public void wait(int seconds) {
           long start = new Date().getTime();
           try {
               driver.wait(seconds * 1000);
           } catch (InterruptedException e) {
               long end = new Date().getTime();
               do {
                   end = new Date().getTime();
               } while ((start + (seconds * 1000)) > end);
           }
       }
    
       /**
        * It sleeps the process for n seconds.
        * @param seconds to be slept.
        */
       public void sleep(long seconds) {
           sleepFor(seconds);
       }
    
       /**
        * It sleeps the process for n seconds.
        * @param seconds to be slept.
        */
       public static void sleepFor(long seconds) {
           long start = new Date().getTime();
           try {
               Thread.sleep(seconds * 1000);
           } catch (InterruptedException e) {
               long end = new Date().getTime();
               do {
                   end = new Date().getTime();
               } while ((start + (seconds * 1000)) > end);
           }
       }
    
       /**
        * @see {@link AppiumDriver#findElements(By)}.
        */
       public List<MobileElement> findElements(By by) {
           List<MobileElement> elements = null;
           try {
               elements = driver.findElements(by);
           } catch (Exception ex) {
               elements = new ArrayList<MobileElement>();
           }
           return elements;
       }
    
       /**
        * @see {@link AppiumDriver#findElementsById(String)}.
        */
       public List<MobileElement> findElementsById(String id) {
           List<MobileElement> elements = null;
           try {
               elements = driver.findElementsById(id);
           } catch (Exception ex) {
               elements = new ArrayList<MobileElement>();
           }
           return elements;
       }
    
       /**
        * @see {@link AppiumDriver#findElementsByLinkText(String)}.
        */
       public List<MobileElement> findElementsByLinkText(String using) {
           List<MobileElement> elements = null;
           try {
               elements = driver.findElementsByLinkText(using);
           } catch (Exception ex) {
               elements = new ArrayList<MobileElement>();
           }
           return elements;
       }
    
       /**
        * @see {@link AppiumDriver#findElementsByPartialLinkText(String)}.
        */
       public List<MobileElement> findElementsByPartialLinkText(String using) {
           List<MobileElement> elements = null;
           try {
               elements = driver.findElementsByPartialLinkText(using);
           } catch (Exception ex) {
               elements = new ArrayList<MobileElement>();
           }
           return elements;
       }
    
       /**
        * @see {@link AppiumDriver#findElementsByTagName(String)}.
        */
       public List<MobileElement> findElementsByTagName(String using) {
           List<MobileElement> elements = null;
           try {
               elements = driver.findElementsByTagName(using);
           } catch (Exception ex) {
               elements = new ArrayList<MobileElement>();
           }
           return elements;
       }
    
       /**
        * @see {@link AppiumDriver#findElementsByName(String)}.
        */
       public List<MobileElement> findElementsByName(String using) {
           List<MobileElement> elements = null;
           try {
               elements = driver.findElementsByName(using);
           } catch (Exception ex) {
               elements = new ArrayList<MobileElement>();
           }
           return elements;
       }
    
       /**
        * @see {@link AppiumDriver#findElementsByClassName(String)}.
        */
       public List<MobileElement> findElementsByClassName(String using) {
           List<MobileElement> elements = null;
           try {
               elements = driver.findElementsByClassName(using);
           } catch (Exception ex) {
               elements = new ArrayList<MobileElement>();
           }
           return elements;
       }
    
       /**
        * @see {@link AppiumDriver#findElementsByCssSelector(String)}.
        */
       public List<MobileElement> findElementsByCssSelector(String using) {
           List<MobileElement> elements = null;
           try {
               elements = driver.findElementsByCssSelector(using);
           } catch (Exception ex) {
               elements = new ArrayList<MobileElement>();
           }
           return elements;
       }
    
       /**
        * @see {@link AppiumDriver#findElementsByXPath(String)}.
        */
       public List<MobileElement> findElementsByXPath(String using) {
           List<MobileElement> elements = null;
           try {
               elements = driver.findElementsByXPath(using);
           } catch (Exception ex) {
               elements = new ArrayList<MobileElement>();
           }
           return elements;
       }
    
       /**
        * @see {@link AppiumDriver#findElementsByAccessibilityId(String)}.
        */
       public List<MobileElement> findElementsByAccessibilityId(String using) {
           List<MobileElement> elements = null;
           try {
               elements = driver.findElementsByAccessibilityId(using);
           } catch (Exception ex) {
               elements = new ArrayList<MobileElement>();
           }
           return elements;
       }
    
       /**
        * @see {@link DefaultGenericMobileDriver#findElement(By)}.
        */
       public MobileElement findElement(By by) {
           MobileElement element;
           try {
               element = driver.findElement(by);
           } catch (Exception ex) {
               element = null;
           }
           return element;
       }
    
       /**
        * @see {@link DefaultGenericMobileDriver#findElementById(String)}.
        */
       public MobileElement findElementById(String id) {
           MobileElement element;
           try {
               element = driver.findElementById(id);
           } catch (Exception ex) {
               element = null;
           }
           return element;
       }
    
       /**
        * @see {@link DefaultGenericMobileDriver#findElementByLinkText(String)}.
        */
       public MobileElement findElementByLinkText(String using) {
           MobileElement element;
           try {
               element = driver.findElementByLinkText(using);
           } catch (Exception ex) {
               element = null;
           }
           return element;
       }
    
       /**
        * @see {@link DefaultGenericMobileDriver#findElementByPartialLinkText(String)}.
        */
       public MobileElement findElementByPartialLinkText(String using) {
           MobileElement element;
           try {
               element = driver.findElementByPartialLinkText(using);
           } catch (Exception ex) {
               element = null;
           }
           return element;
       }
    
       /**
        * @see {@link DefaultGenericMobileDriver#findElementByTagName(String)}.
        */
       public MobileElement findElementByTagName(String using) {
           MobileElement element;
           try {
               element = driver.findElementByTagName(using);
           } catch (Exception ex) {
               element = null;
           }
           return element;
       }
    
       /**
        * @see {@link DefaultGenericMobileDriver#findElementByName(String)}.
        */
       public MobileElement findElementByName(String using) {
           MobileElement element;
           try {
               element = driver.findElementByName(using);
           } catch (Exception ex) {
               element = null;
           }
           return element;
       }
    
       /**
        * @see {@link AppiumDriver#getExecuteMethod()}.
        */
       public ExecuteMethod getExecuteMethod() {
           return driver.getExecuteMethod();
       }
    
       /**
        * @see {@link AppiumDriver#resetApp()}.
        */
       public void resetApp() {
           driver.resetApp();
       }
    
       /**
        * @see {@link AppiumDriver#isAppInstalled(String)}.
        */
       public boolean isAppInstalled(String bundleId) {
           return driver.isAppInstalled(bundleId);
       }
    
       /**
        * @see {@link AppiumDriver#installApp(String)}.
        */
       public void installApp(String appPath) {
           driver.installApp(appPath);
       }
    
       /**
        * @see {@link AppiumDriver#removeApp(String)}.
        */
       public void removeApp(String bundleId) {
           driver.removeApp(bundleId);
       }
    
       /**
        * @see {@link AppiumDriver#launchApp()}.
        */
       public void launchApp() {
           driver.launchApp();
       }
    
       /**
        * @see {@link AppiumDriver#closeApp()}.
        */
       public void closeApp() {
           driver.closeApp();
       }
    
       /**
        * @see {@link AppiumDriver#runAppInBackground(int)}.
        */
       public void runAppInBackground(int seconds) {
           driver.runAppInBackground(seconds);
       }
    
       /**
        * @see {@link AppiumDriver#hideKeyboard()}.
        */
       public void hideKeyboard() {
           driver.hideKeyboard();
       }
    
       /**
        * @see {@link AppiumDriver#pullFile(String)}.
        */
       public byte[] pullFile(String remotePath) {
           return driver.pullFile(remotePath);
       }
    
       /**
        * @see {@link AppiumDriver#pullFolder(String)}.
        */
       public byte[] pullFolder(String remotePath) {
           return driver.pullFolder(remotePath);
       }
    
       /**
        * @see {@link AppiumDriver#performTouchAction(TouchAction)}.
        */
       public TouchAction performTouchAction(TouchAction touchAction) {
           return driver.performTouchAction(touchAction);
       }
    
       /**
        * @see {@link AppiumDriver#performMultiTouchAction(MultiTouchAction)}.
        */
       public void performMultiTouchAction(MultiTouchAction multiAction) {
           driver.performMultiTouchAction(multiAction);
       }
    
       /**
        * This method is the same than {@link DevTest#tap(int, WebElement, int)} but using a {@link MobileElement} object.
        */
       public void tap(int fingers, MobileElement element, int duration) {
           int xPosition = element.getLocation().getX() + element.getSize().getWidth() / 2;
           int yPosition = element.getLocation().getY() + element.getSize().getHeight() / 2;
           this.tap(fingers, xPosition, yPosition, duration);
       }
    
       /**
        * @see {@link AppiumDriver#tap(int, WebElement, int)}.
        */
       public void tap(int fingers, WebElement element, int duration) {
           driver.tap(fingers, element, duration);
       }
    
       /**
        * @see {@link AppiumDriver#tap(int, int, int, int)}.
        */
       public void tap(int fingers, int x, int y, int duration) {
           driver.tap(fingers, x, y, duration);
       }
    
       /**
        * @see {@link AppiumDriver#swipe(int, int, int, int, int)}.
        */
       public void swipe(int startx, int starty, int endx, int endy, int duration) {
           driver.swipe(startx, starty, endx, endy, duration);
       }
    
       /**
        * @see {@link AppiumDriver#pinch(WebElement)}.
        */
       public void pinch(WebElement el) {
           driver.pinch(el);
       }
    
       /**
        * @see {@link AppiumDriver#pinch(int, int)}.
        */
       public void pinch(int x, int y) {
           driver.pinch(x, y);
       }
    
       /**
        * @see {@link AppiumDriver#zoom(WebElement)}.
        */
       public void zoom(WebElement el) {
           driver.zoom(el);
       }
    
       /**
        * @see {@link AppiumDriver#zoom(int, int)}.
        */
       public void zoom(int x, int y) {
           driver.zoom(x, y);
       }
    
       /**
        * @see {@link AppiumDriver#getSettings()}.
        */
       public JsonObject getSettings() {
           return driver.getSettings();
       }
    
       /**
        * @see {@link AppiumDriver#context(String)}.
        */
       public WebDriver context(String name) {
           return driver.context(name);
       }
    
       /**
        * @see {@link AppiumDriver#getContextHandles()}.
        */
       public Set<String> getContextHandles() {
           return driver.getContextHandles();
       }
    
       /**
        * @see {@link AppiumDriver#getContext()}.
        */
       public String getContext() {
           return driver.getContext();
       }
    
       /**
        * @see {@link AppiumDriver#rotate(ScreenOrientation)}.
        */
       public void rotate(ScreenOrientation orientation) {
           driver.rotate(orientation);
       }
    
       /**
        * @see {@link AppiumDriver#getOrientation()}.
        */
       public ScreenOrientation getOrientation() {
           return driver.getOrientation();
       }
    
       /**
        * @see {@link AppiumDriver#location()}.
        */
       public Location location() {
           return driver.location();
       }
    
       /**
        * @see {@link AppiumDriver#setLocation(Location)}.
        */
       public void setLocation(Location location) {
           driver.setLocation(location);
       }
    
       /**
        * @see {@link AppiumDriver#getAppStrings()}.
        */
       public Map<String, String> getAppStrings() {
           return driver.getAppStringMap();
       }
    
       /**
        * @see {@link AppiumDriver#getAppStrings(String)}.
        */
       public Map<String, String> getAppStringMap(String language) {
           return driver.getAppStringMap(language);
       }
    
       /**
        * @see {@link AppiumDriver#getRemoteAddress()}.
        */
       public URL getRemoteAddress() {
           return driver.getRemoteAddress();
       }
    
       /**
        * @see {@link RemoteWebDriver#quit()}.
        */
       public void quit() {
           driver.quit();
       }
    
       /**
        * @see {@link RemoteWebDriver#getWindowHandles()}
        */
       public Set<String> getWindowHandles() {
           return driver.getWindowHandles();
       }
    
       /**
        * @see {@link RemoteWebDriver#switchTo()}
        */
       public TargetLocator switchTo() {
           return driver.switchTo();
       }
    
       /**
        * @see {@link RemoteWebDriver#getWindowHandle()}
        */
       public String getWindowHandle() {
           return driver.getWindowHandle();
       }
    
       /**
        * Adding support to execute JavaScript codes.
        * @param script to be executed.
        * @return the result of the execution.
        */
       public Object executeJavaScript(String script) {
           JavascriptExecutor jsExecutor = (JavascriptExecutor) driver;
           Object output = jsExecutor.executeScript(script);
           return output;
       }
    
       /**
        * Providing a way to get the native driver.
        * @return the native {@link AppiumDriver} instance.
        */
       public AppiumDriver<MobileElement> getDriver() {
           return driver;
       }
    
    }
  • 相关阅读:
    关于COM+的一些回顾
    Workflow Foundation 4.0中的事件驱动流程设计和应用(四)
    html5及其相关学习资源介绍
    ADO.NET Data Service如何直接支持用Json格式返回数据
    《实践与思考》系列连载(5)——问答Hprose,以及关于技术与开源的思考
    .NET Framework 4新特性之 Type Equivalence(等价类型)
    做软件也是要有点追求的
    Console的默认Encoding问题
    《实践与思考》系列连载(6)——IT从业人员工作环境及状态调查 抽奖结果公布
    Workflow Foundation 4.0中的事件驱动流程设计和应用(一)
  • 原文地址:https://www.cnblogs.com/testway/p/6959692.html
Copyright © 2011-2022 走看看