This commit is contained in:
Sophie Lathouwers 2017-06-28 10:18:26 +00:00 committed by GitHub
commit 28df2847fa
18 changed files with 618 additions and 250 deletions

View file

@ -1,2 +1,33 @@
language: java language: java
sudo: false
install: true
services:
- docker
addons:
sonarcloud:
organization: "bpsdproject"
token:
secure: "FKHLAA1VC6XvrJTDFXwh7IvHAgX+hHkzuGmiOJ9RvguTkP8yQTQqczQA0QFUaCKoOXLQf7n1Xadx60vKDxk1FQS4GxhvB1n9muvhjkLOXstpRjKEBL/t3CgVeIYPdf676lT/vED2yeFmTC0BcC82k2h8dNpBpr2c/iRsHriiyf8NOOM9XO6sbDfPPGYhTzkuBRhplVZ6723z5KaHDvSgjQmT/dlIZkY00fqyVPI20JZqvScbR0/8QjED8jWtnlZpzU02lIrpkcIJF2gS4OGkucE1GBvJtkAejm5V2g++mdj3B+ja4x2rZmLmAhOVV2PzRatMSrEyoSCRpjXMf39WaTlehaii2foWPvj+CTl4iO4ApdVluFufS5EuPknJ3/pOQVAilR0qad3NBokUGCnfGhAqo06AA0aeLb05A3nfhT/CeOPEklwpkWNHNG6B4XWkNUH54WemqvmDvUjPspSbFlU3RBwjVcacj4Dyuh4eo48E/+bYnoIhaAZiyh/SXqrIx9Wh0COxYv1tgcqJ/xbDtJf3CZdjGZfKCpC3OUCliRTHtKQrzxQxai4oTlTtGTWhxm81BIOStRY0IgkTGvMiymv91wcWN6Q937D4CsRGJ7O4ZKWeqPXCmntVVqEiWd5av2Lpnv7W2QnajkcNZ4UyGtH3mzo4OykKGc9EV9fcu64="
jdk: oraclejdk8 jdk: oraclejdk8
install:
- docker-compose build
before_script:
- docker-compose up -d
script:
- mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent test -Dskip.failsafe.tests sonar:sonar -Dsonar.pitest.mode=reuseReport
- docker exec springpetclinic_pet-clinic_1 mvn verify -Dskip.surefire.tests -q --batch-mode
after_script:
- docker-compose stop
- docker-compose rm -f
cache:
directories:
- '$HOME/.m2/repository'
- '$HOME/.sonar/cache'

18
Dockerfile Normal file
View file

@ -0,0 +1,18 @@
FROM maven:latest
# Set the workdir
WORKDIR /app
# Copy the source to the container
COPY . .
# Build the project
RUN mvn package -Dskip.failsafe.tests -q --batch-mode && \
mvn org.pitest:pitest-maven:mutationCoverage -q --batch-mode
# Copy and make the jar executable
RUN sh -c 'mkdir dist/ && cp -a target/. dist/ && touch dist/spring-petclinic-*.jar'
#Start the project
ENV JAVA_OPTS=""
ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar dist/spring-petclinic-*.jar" ]

View file

@ -1,9 +1,23 @@
mysql: version: '2'
image: mysql services:
pet-clinic:
build: .
ports: ports:
- "3306:3306" - "8080:8080"
environment: links:
- MYSQL_ROOT_PASSWORD=root - selenium
- MYSQL_DATABASE=test # - mysql:mysql
volumes: # mysql:
- "./conf.d:/etc/mysql/conf.d:ro" # image: mysql
# ports:
# - "3306:3306"
# environment:
# - MYSQL_ROOT_PASSWORD=root
# - MYSQL_DATABASE=test
# volumes:
# - "./conf.d:/etc/mysql/conf.d:ro"
selenium:
image: selenium/standalone-firefox
shm_size: 2g
ports:
- "4444:4444"

48
pom.xml
View file

@ -3,7 +3,7 @@
xmlns="http://maven.apache.org/POM/4.0.0" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.samples</groupId> <groupId>org.springframework.samples.bpsd</groupId>
<artifactId>spring-petclinic</artifactId> <artifactId>spring-petclinic</artifactId>
<version>1.5.1</version> <version>1.5.1</version>
@ -31,6 +31,7 @@
<cobertura.version>2.7</cobertura.version> <cobertura.version>2.7</cobertura.version>
<selenium-java.version>3.4.0</selenium-java.version>
</properties> </properties>
<dependencies> <dependencies>
@ -116,10 +117,55 @@
<artifactId>spring-boot-devtools</artifactId> <artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
<!--Selenium-->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>${selenium-java.version}</version>
</dependency>
</dependencies> </dependencies>
<build> <build>
<plugins> <plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<!-- skips surefire tests without skipping failsafe tests.
Property value seems to magically default to false -->
<skipTests>${skip.surefire.tests}</skipTests>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<configuration>
<!-- skips failsafe tests without skipping surefire tests.
Property value seems to magically default to false -->
<skipTests>${skip.failsafe.tests}</skipTests>
</configuration>
<executions>
<execution>
<id>run-integration-tests</id>
<phase>integration-test</phase>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.pitest</groupId>
<artifactId>pitest-maven</artifactId>
<version>1.2.0</version>
<configuration>
<outputFormats>
<outputFormat>XML</outputFormat>
</outputFormats>
</configuration>
</plugin>
<plugin> <plugin>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId> <artifactId>spring-boot-maven-plugin</artifactId>

View file

@ -28,7 +28,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication @SpringBootApplication
public class PetClinicApplication { public class PetClinicApplication {
public static void main(String[] args) throws Exception { public static void main(String[] args) {
SpringApplication.run(PetClinicApplication.class, args); SpringApplication.run(PetClinicApplication.class, args);
} }

View file

@ -126,13 +126,13 @@ public class Owner extends Person {
* @param name to test * @param name to test
* @return true if pet name is already in use * @return true if pet name is already in use
*/ */
public Pet getPet(String name, boolean ignoreNew) { public Pet getPet(final String name, final boolean ignoreNew) {
name = name.toLowerCase(); String lowerCaseName = name.toLowerCase();
for (Pet pet : getPetsInternal()) { for (Pet pet : getPetsInternal()) {
if (!ignoreNew || !pet.isNew()) { if (!ignoreNew || !pet.isNew()) {
String compName = pet.getName(); String compName = pet.getName();
compName = compName.toLowerCase(); compName = compName.toLowerCase();
if (compName.equals(name)) { if (compName.equals(lowerCaseName)) {
return pet; return pet;
} }
} }

View file

@ -78,7 +78,7 @@ class OwnerController {
} }
@RequestMapping(value = "/owners", method = RequestMethod.GET) @RequestMapping(value = "/owners", method = RequestMethod.GET)
public String processFindForm(Owner owner, BindingResult result, Map<String, Object> model) { public String processFindForm(final Owner owner, final BindingResult result, final Map<String, Object> model) {
// allow parameterless GET request for /owners to return all records // allow parameterless GET request for /owners to return all records
if (owner.getLastName() == null) { if (owner.getLastName() == null) {
@ -93,8 +93,8 @@ class OwnerController {
return "owners/findOwners"; return "owners/findOwners";
} else if (results.size() == 1) { } else if (results.size() == 1) {
// 1 owner found // 1 owner found
owner = results.iterator().next(); Owner foundOwner = results.iterator().next();
return "redirect:/owners/" + owner.getId(); return "redirect:/owners/" + foundOwner.getId();
} else { } else {
// multiple owners found // multiple owners found
model.put("selections", results); model.put("selections", results);

View file

@ -31,8 +31,7 @@ class CrashController {
@RequestMapping(value = "/oups", method = RequestMethod.GET) @RequestMapping(value = "/oups", method = RequestMethod.GET)
public String triggerException() { public String triggerException() {
throw new RuntimeException( throw new ExampleException("Expected: controller used to showcase what happens when an exception is thrown");
"Expected: controller used to showcase what " + "happens when an exception is thrown");
} }
} }

View file

@ -0,0 +1,15 @@
package org.springframework.samples.petclinic.system;
/**
* @author Martijn
* @since 21-6-2017.
*/
public class ExampleException extends RuntimeException {
public ExampleException(String message) {
super(ExampleException.class.getSimpleName() + ": " + message);
}
protected ExampleException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(ExampleException.class.getSimpleName() + ": " + message, cause, enableSuppression, writableStackTrace);
}
}

View file

@ -31,11 +31,11 @@ import org.springframework.web.bind.annotation.ResponseBody;
@Controller @Controller
class VetController { class VetController {
private final VetRepository vets; private final VetRepository vetRepository;
@Autowired @Autowired
public VetController(VetRepository clinicService) { public VetController(VetRepository clinicService) {
this.vets = clinicService; this.vetRepository = clinicService;
} }
@RequestMapping(value = { "/vets.html" }) @RequestMapping(value = { "/vets.html" })
@ -43,7 +43,7 @@ class VetController {
// Here we are returning an object of type 'Vets' rather than a collection of Vet // Here we are returning an object of type 'Vets' rather than a collection of Vet
// objects so it is simpler for Object-Xml mapping // objects so it is simpler for Object-Xml mapping
Vets vets = new Vets(); Vets vets = new Vets();
vets.getVetList().addAll(this.vets.findAll()); vets.getVetList().addAll(this.vetRepository.findAll());
model.put("vets", vets); model.put("vets", vets);
return "vets/vetList"; return "vets/vetList";
} }
@ -53,7 +53,7 @@ class VetController {
// Here we are returning an object of type 'Vets' rather than a collection of Vet // Here we are returning an object of type 'Vets' rather than a collection of Vet
// objects so it is simpler for JSon/Object mapping // objects so it is simpler for JSon/Object mapping
Vets vets = new Vets(); Vets vets = new Vets();
vets.getVetList().addAll(this.vets.findAll()); vets.getVetList().addAll(this.vetRepository.findAll());
return vets; return vets;
} }

View file

@ -18,7 +18,6 @@ package org.springframework.samples.petclinic.vet;
import java.util.Collection; import java.util.Collection;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
import org.springframework.dao.DataAccessException;
import org.springframework.data.repository.Repository; import org.springframework.data.repository.Repository;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -40,7 +39,7 @@ public interface VetRepository extends Repository<Vet, Integer> {
*/ */
@Transactional(readOnly = true) @Transactional(readOnly = true)
@Cacheable("vets") @Cacheable("vets")
Collection<Vet> findAll() throws DataAccessException; Collection<Vet> findAll();
} }

View file

@ -30,14 +30,14 @@ import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement @XmlRootElement
public class Vets { public class Vets {
private List<Vet> vets; private List<Vet> vetList;
@XmlElement @XmlElement
public List<Vet> getVetList() { public List<Vet> getVetList() {
if (vets == null) { if (vetList == null) {
vets = new ArrayList<>(); vetList = new ArrayList<>();
} }
return vets; return vetList;
} }
} }

View file

@ -17,7 +17,6 @@ package org.springframework.samples.petclinic.visit;
import java.util.List; import java.util.List;
import org.springframework.dao.DataAccessException;
import org.springframework.data.repository.Repository; import org.springframework.data.repository.Repository;
import org.springframework.samples.petclinic.model.BaseEntity; import org.springframework.samples.petclinic.model.BaseEntity;
@ -38,7 +37,7 @@ public interface VisitRepository extends Repository<Visit, Integer> {
* @param visit the <code>Visit</code> to save * @param visit the <code>Visit</code> to save
* @see BaseEntity#isNew * @see BaseEntity#isNew
*/ */
void save(Visit visit) throws DataAccessException; void save(Visit visit);
List<Visit> findByPetId(Integer petId); List<Visit> findByPetId(Integer petId);

View file

@ -0,0 +1,52 @@
package nl.utwente.bpsd.selenium;
import org.junit.Assert;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.openqa.selenium.By;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.Select;
import java.net.MalformedURLException;
/**
* @author Martijn
* @since 21-6-2017.
*/
public class AddOwnerIT extends SeleniumBaseIT {
public AddOwnerIT() throws MalformedURLException {
super();
}
@Test
@Category(SeleniumBaseIT.class)
public void addOwnerIT() {
driver.get(BASE_URL+"/owners/new");
//Add an owner
fillTextField(By.name("firstName"), "Sophie");
fillTextField(By.name("lastName"), "Lathouwers");
fillTextField(By.name("address"), "Homeroad 12");
fillTextField(By.name("city"), "Enschede");
fillTextField(By.name("telephone"), "0534890000");
driver.findElement(By.name("telephone")).submit();
waitForPageToLoad();
waitFor(ExpectedConditions.presenceOfElementLocated(By.xpath("//*[text()='Sophie Lathouwers']")));
Assert.assertTrue("Could not locate \"Sophie Lathouwers\" on the page. This is the html of the current page: "+getHTML(), pageContainsText("Sophie Lathouwers"));
//Add a pet
//The link text has \n thats why it cannot find it
waitFor(ExpectedConditions.presenceOfAllElementsLocatedBy(By.partialLinkText("Add")));
driver.findElement(By.partialLinkText("Add")).click();
waitForPageToLoad();
fillTextField(By.name("name"), "Thumper");
fillTextField(By.name("birthDate"), "1942/08/09");
new Select(driver.findElement(By.name("type"))).selectByValue("hamster");
driver.findElement(By.name("name")).submit();
waitForPageToLoad();
Assert.assertTrue("Could not locate \"Thumper\" on the page. This is the html of the current page: "+getHTML(),pageContainsText("Thumper"));
setTestFinished();
}
}

View file

@ -0,0 +1,60 @@
package nl.utwente.bpsd.selenium;
import org.junit.Assert;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import java.net.MalformedURLException;
import java.util.List;
/**
* @author Sophie
* @since 21-6-2017.
*/
public class AddVisitIT extends SeleniumBaseIT {
public AddVisitIT() throws MalformedURLException {
super();
}
@Test
@Category(SeleniumBaseIT.class)
public void editPetNameAndAddAVisit() {
driver.get(BASE_URL+"/owners/find");
driver.findElement(By.name("lastName")).submit();
waitForPageToLoad();
//Go to first owner
WebElement table = driver.findElement(By.tagName("table"));
List<WebElement> cells = table.findElements(By.xpath(".//tr/td"));
cells.get(0).findElement(By.xpath("a")).click();
waitForPageToLoad();
//Go to edit page of first pet
driver.findElement(By.xpath("//table//tr/td/table/tbody//a[contains(text(),'Edit')]")).click();
waitForPageToLoad();
//Edit Name of pet
fillTextField(By.name("name"), "foobar");
driver.findElement(By.name("name")).submit();
waitForPageToLoad();
Assert.assertNotNull(driver.findElement(By.xpath("//table//tr/td/dl/dd[contains(text(), 'foobar')]")));
driver.findElement(By.xpath("//table//tr/td/table/tbody//a[contains(text(),'Add')]")).click();
waitForPageToLoad();
fillTextField(By.name("date"), "2017/12/12");
fillTextField(By.name("description"), "Foobar check for disease");
driver.findElement(By.name("date")).submit();
waitForPageToLoad();
Assert.assertNotNull(driver.findElement(By.xpath("//table//tr/td/table/tbody//td[contains(text(), '2017-12-12')]")));
setTestFinished();
}
}

View file

@ -0,0 +1,33 @@
package nl.utwente.bpsd.selenium;
import org.junit.Assert;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.openqa.selenium.By;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.net.MalformedURLException;
/**
*
* @author Martijn
* @since 21-6-2017.
*/
public class FindOwnerIT extends SeleniumBaseIT {
public FindOwnerIT() throws MalformedURLException {
super();
}
@Test
@Category(SeleniumBaseIT.class)
public void findOwnerIT() {
driver.get(BASE_URL+"/owners/find");
fillTextField(By.name("lastName"),"Coleman");
driver.findElement(By.name("lastName")).submit();
waitForPageToLoad();
Assert.assertTrue("Could not find \"Jean Coleman\" on the current page. This is the html of the current page: "+getHTML(),driver.findElements(By.xpath("//*[text()='Jean Coleman']")).size() == 1);
setTestFinished();
}
}

View file

@ -0,0 +1,102 @@
package nl.utwente.bpsd.selenium;
import org.junit.After;
import org.openqa.selenium.*;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.remote.Augmenter;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.logging.Logger;
/**
* @author Martijn
* @since 21-6-2017.
*/
public class SeleniumBaseIT {
protected final WebDriver driver;
private boolean testFinished = false;
public static final String BASE_URL = "http://pet-clinic:8080/";
// public static final String BASE_URL = "http://localhost:8080/";
public SeleniumBaseIT() throws MalformedURLException {
// System.setProperty("webdriver.chrome.driver","C:\\Users\\marti\\Downloads\\chromedriver_win32\\chromedriver.exe");
// this.driver = new ChromeDriver();
this.driver = new Augmenter().augment(new RemoteWebDriver(new URL("http://selenium:4444/wd/hub"), DesiredCapabilities.firefox()));
driver.get(BASE_URL);
}
public void fillTextField(By by, String text) {
driver.findElement(by).clear();
driver.findElement(by).sendKeys(text);
}
protected void setTestFinished() {
testFinished = true;
}
@After
public void afterTest() {
if (!testFinished) {
if (driver instanceof TakesScreenshot) {
File screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
Logger.getLogger(this.getClass().getName()).warning(screenshot.getAbsolutePath());
}
}
driver.close();
}
protected boolean pageContainsText(String text) {
return driver.findElements(By.xpath("//*[text()='" + text + "']")).size() == 1;
}
public String getHTML() {
return driver.findElement(By.xpath("//html")).getAttribute("innerHTML");
}
protected void waitForPageToLoad() {
waitFor(new FixedPeriod(333));
waitFor(new PageLoadedExpectedCondition());
}
protected void waitFor(ExpectedCondition<?> condition) {
new WebDriverWait(driver, 3).until(condition);
}
private class PageLoadedExpectedCondition implements ExpectedCondition<Boolean> {
@Override
public Boolean apply(WebDriver webDriver) {
if (webDriver instanceof JavascriptExecutor) {
return ((JavascriptExecutor) webDriver).executeScript("return document.readyState").equals("complete");
}
throw new ClassCastException("This webdriver is not able to execute javascript.");
}
}
protected class FixedPeriod implements ExpectedCondition<Boolean> {
private final int time;
public FixedPeriod(int timeInMilliseconds) {
this.time = timeInMilliseconds;
}
@Override
public Boolean apply(WebDriver webDriver) {
try {
Thread.sleep(time);
return true;
} catch (InterruptedException e) {
e.printStackTrace();
return false;
}
}
}
}