In einem unserer Projekte haben wir eine Webanwendung erstellt, für die wir einen automatischen Integrationstest brauchten. Diese Webanwendung ist allerdings nur von bestimmten IP-Adressen zu erreichen, weshalb der Test seine Http-Requests durch einen Proxy leiten muss, der eine Authentifizierung erfordert. Eigentlich ein alltägliches Szenario: Schließlich soll nicht jeder Zugriff auf eine Applikation haben, bevor der Zeitpunkt des Livegangs gekommen ist.
Das meiner Meinung nach beste Tool für diese Art von Tests auf Webanwendungen ist Selenium. Dieses stellt nicht nur mit der Selenium IDE eine tolle Werkzeugunterstützung für die Testentwicklung zur Verfügung, sondern stellt auch verschiedene „Driver“ bereit – dies ermöglicht es, die gleiche Testlogik mit verschiedenen Browsern zu verwenden (oder falls gewünscht mit HtmlUnit). Leider stellt sich die Proxykonfiguration als nicht ganz trivial dar. So lässt sich zwar ein Proxy einstellen, aber die Authentifizierung erweist sich als Problem, das sich mit Bordmitteln des Frameworks nicht lösen läßt.
Im Netz kursieren Lösungen, die das Einrichten eines mit bestimmten Plugins und Konfiguration ausgestatteten Firefox-Profils vorschlagen, das dann im Test benutzt wird. Diese Lösung hat klare Nachteile: Sie ist nicht browserunabhängig und erfordert ein hohes Maß an Aufwand für die Pflege und Verteilung des Profils. Stattdessen gibt es einen Weg, browserunabhängig die Proxyauthentifizierung zu meistern: und zwar durch einen weiteren Proxy!
Neben Selenium (sie können auch ein beliebiges anderes Tool benutzen) braucht es für diese Lösung das Projekt browsermob-proxy. In Maven sehen die Abhängigkeiten dann zusammen mit Selenium und Junit beispielsweise so aus:
<dependencies> <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-java</artifactId> <version>2.12.0</version> <scope>test</scope> </dependency> <dependency> <groupId>biz.neustar</groupId> <artifactId>browsermob-proxy</artifactId> <version>2.0-beta-3</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.10</version> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.1.2</version> </dependency> </dependencies> </dependencyManagement>
Wichtig ist dabei die Festsetzung der Version 4.1.2 für den httpClient, da ansonsten eine ältere Version den Versionskonflikt für sich entscheidet, was zu einer ClassNotFoundException
zur Laufzeit führt.
Mit diesen Abhängigkeiten lässt sich nun programmatisch der Proxyserver erstellen. Einziger Schönheitsfehler bei dieser Lösung ist, dass der Proxy den direkten Zugriff des Clients auf den httpClient des Proxys nicht zulässt. Daher lässt sich die Proxykonfiguration nur via Reflection tätigen.
Mit wenigen Zeilen lässt sich so der Proxyserver konfigurieren:
/** * * @param internalPort Port of the local proxy (listening on localhost). * @param proxyHost The (external) proxy to use. * @param credentials The credentials needed for the external proxy. * @return The created proxy server. * @throws Exception */ private ProxyServer createAndConfigureProxyServer(int internalPort, HttpHost proxyHost, UsernamePasswordCredentials credentials) throws Exception{ // Set up the internal proxy server and start it, because otherwise the // http client is not started and we end up with a NP. ProxyServer server = new ProxyServer(internalPort); server.start(); DefaultHttpClient httpClient = extract(); AuthScope authScope = new AuthScope(proxyHost.getHostName(), proxyHost.getPort()); httpClient.getCredentialsProvider().setCredentials(authScope, credentials); HttpHost proxy = new HttpHost(proxyHost.getHostName(), proxyHost.getPort()); httpClient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy); return server; } private DefaultHttpClient extract() throws NoSuchFieldException, IllegalAccessException { // The httpclient has to be manipulated in order to be configured // to use the proxy is private, so we have to rely on reflection. Field clientField = ProxyServer.class.getDeclaredField("client"); setFieldAccessible(clientField); BrowserMobHttpClient client = (BrowserMobHttpClient) clientField .get(server); Field httpClientfield = BrowserMobHttpClient.class .getDeclaredField("httpClient"); setFieldAccessible(httpClientfield); DefaultHttpClient httpClient = (DefaultHttpClient) httpClientfield .get(client); return httpClient; } private void setFieldAccessible(Field clientField) { if (!clientField.isAccessible()) { clientField.setAccessible(true); } }
Der Proxyserver existiert nun und ist gestartet, so dass wir uns jetzt nur noch um den Test kümmern müssen – zum Beispiel mit dem FirefoxDriver:
ProxyServer server = createAndConfigureProxyServer(); FirefoxProfile firefoxProfile = new FirefoxProfile(); firefoxProfile.setProxyPreferences(server.seleniumProxy()); FirefoxDriver driver = new FirefoxDriver(firefoxProfile);
Nach Abschluss aller Tests sollte man nicht vergessen, den Server auch zu stoppen.
Happy testing!