inPractice
Scala
patric.fornasier@thoughtworks.com marc.hofer@thoughtworks.com
in Practice Scala patric.fornasier@thoughtworks.com - - PowerPoint PPT Presentation
in Practice Scala patric.fornasier@thoughtworks.com marc.hofer@thoughtworks.com e c n e i r e p x E Optimising IT n g i s e D Organisations Consulting Build & Release Management technology esting T Offices 2 Warning
inPractice
Scala
patric.fornasier@thoughtworks.com marc.hofer@thoughtworks.com
Optimising IT Organisations technology
Consulting
T esting Build & Release Management E x p e r i e n c e D e s i g n
Offices
2
presentation Contains code examples
A bit of about the project
Scala
Functional Object Oriented Statically Typed Scriptable JVM- based
JVM Byte Code Compile Scala
“Functional Programming”
List<String> features = Arrays.asList("noFeature", "featureC", "featureB", "featureA"); for (String feature : features) { System.out.println(feature); }
List<String> javaFeatures = Arrays.asList("noFeature", "featureC", "featureB", "featureA"); for (String feature : javaFeatures) { System.out.println(feature); } val scalaFeatures = List( "noFeature", "featureC", "featureB", "featureA") scalaFeatures foreach { println _ }
val currentFeatures = List("noFeature", "featureC", "featureB", "featureA") Result: "featureA_toggle, featureB_toggle" currentFeatures.filter { _.startsWith("feature") } .mkString(", ") .map { _ + "_toggle" } .sorted .take(2)
String result = ""; List<String> currentFeatures = Arrays.asList("noFeature", "featureC", "featureB", "featureA"); Collections.sort(currentFeatures); int i = 0, j = 0, take = 2; while(i < take && j < currentFeatures.size()) { String feature = currentFeatures.get(j++); if(feature.startsWith("feature")) { result += feature + "_toggle" + ((i++ != take) ? ", " : ""); } } currentFeatures.filter { _.startsWith("feature") } .mkString(", ") .map { _ + "_toggle" } .sorted .take(2)
“Less Boilerplate”
public class Property {}
class Property {}
public class Property { private final String key; private final String value; private final String defaultValue; public Property(String key, String value, String defaultValue) { this.key = key; this.value = value; this.defaultValue = defaultValue; } }
class Property(key: String, value: String, default: String) {}
public class Property { // ... public String getKey() { return key; } public String getValue() { return value; } public String getDefaultValue() { return defaultValue; } }
class Property(val key: String, val value: String, val default: String) {}
public class Property { // ... public Property changeTo(String newValue) { return new Property(key, value, newValue); } public Property reset() { return new Property(key, value); } @Override public String toString() { return String.format("%s=%s", key, value); } }
class Property(val key: String, val value: String, val default: String) { def changeTo(newValue: String) = new Property(key, newValue, default) def reset = new Property(key, default)
}
public class Property { // ... @Override public boolean equals(Object obj) { if (!(obj instanceof Property)) return false; Property other = (Property) obj; return other.key.equals(key) && other.value.equals(value) && other.defaultValue.equals(defaultValue) } @Override public int hashCode() { int result = 17; result = 31 * result + key; result = 31 * result + value; result = 31 * result + defaultValue; return result; } }
case class Property(key: String, value: String, default: String) { def changeTo(newValue: String) = Property(key, newValue, default) def reset = Property(key, default)
}
case class Property(key: String, value: String, default: String) { def changeTo(newValue: String) = Property(key, newValue, default) def reset = Property(key, default)
}
45 vs 5
L O C s
* ignoring blank linesAnother Example: { Blocks }
“Less Boilerplate”
val content = get("http://foo.com") { toString } val statusCode = get("http://foo.com") { statusCode } get("http://foo.com") int statusCode = httpSupport.get("http://foo.com", new Block<Integer>() { public Integer execute(HttpResponse response) { return response.getStatusLine().getStatusCode()); } }); String content = httpSupport.get("http://foo.com", new Block<String>() { public Integer execute(HttpResponse response) { return EntityUtils.toString(response.getEntity()); } }); httpSupport.get("http://foo.com", new VoidBlock() { public void execute(HttpResponse response) {} });
“Java Compatibility”
java -cp "lib/*" com.springer.core.app.Casper
Scala library goes in here!
val java = new java.util.ArrayList[String] val scala: Seq[String] = foo filter { ... }
val log = Logger.getLogger("com.foo") log.debug("Hello!")
“Gentle Learning Curve”
“Java without ;”
Library re-use
JVM
<XML-Support/>
<PdfInfo hasAccess="false">
<DDSId>12345</DDSId> <ContentType>Article</ContentType> </PdfInfo>
val pdfInfo = val contentType = (pdfInfo \ "ContentType").text val hasAccess = (pdfInfo \ "@hasAccess").text.toBoolean
val pdfInfoList = <PdfInfoList>
<PdfInfo hasAccess="false"> <DDSId>12345</DDSId> <ContentType>Article</ContentType> </PdfInfo> <PdfInfo hasAccess="true"> <DDSId>54321</DDSId> <ContentType>Book</ContentType> </PdfInfo> </PdfInfoList>
case class PdfInfo(xml: NodeSeq) { val ddsId = (xml \ "DDSId").head.text val hasAccess = (xml \ "@hasAccess").head.text val contentType = (xml \ "ContentType").head.text } (pdfInfoList \ "PdfInfo") map { PdfInfo(_) }
“DSL- Friendly”
System App2 App3 Functional App1 Unit DB Integration
Levels of Testing
Unit Test DSL
class PropertyTest extends Spec with ShouldMatchers { it("should render key and value separated with an equals sign") { Property("shark", "fish").toString should equal ("shark=fish") } }
class ForgottenPasswordPageTests extends FunctionalTestSpec with ForgottenPasswordSteps with Uris { it("should validate user email") { given(iNavigateTo(ForgottenPasswordPage)) when(iSubmitEmail("invalid@email.com")) then(iShouldSeeInvalidEmailErrorMessage) } }
Functional Test DSL
Document Validation DSL
trait DDSValidationRules extends JournalXPaths with APlusPlusElements { val journalRules = List( No(JournalTitle) is Invalid, MoreThanOneElementIn(JournalArticle) is Invalid, BadLanguageIn(JournalArticleLanguage) is Invalid, JournalIssue With No(JournalIssueCoverYear) is Invalid, Bibliography With No(BibliographyHeading) is Invalid, MarkupIn(JournalTitle) is Unsupported, MarkupIn(JournalAuthor) is Unsupported ) }
“Active Community”
“Fun”
“Fun”
“Fun”
val foo = (7 to 21)
“Fun”
List<Integers> foo = new ArrayList<Integers>() for (int i = 7; i <= 21; i++) { foo.add(i); }
“Fun”
def eat(food: String = "plankton")
“Fun”
public void eat(String food) {...} public void eat() { eat("plankton"); }
get("http://foo.com") { statusCode }
“Fun”
httpSupport.get("http://foo.com", new Block<Integer>() { public Integer execute(HttpResponse response) { return response.getStatusLine().getStatusCode()); } });
“Fun”
“Tool Support”
“More rope...”
Scala Collection Library
class JournalPageTests extends Spec with FunctionalTestSpec with JournalPageSteps with SearchResultsPageSteps with LanguageSteps with LanguageSupport with JavascriptSupport with Uris with IssuePageSteps with VolumesAndIssuesPageSteps with CoverImageUrlBuilders with FakeEntitlementSteps with FullTextPageSteps with CommonAbstractSteps with GoogleAnalyticsSteps with ActionSectionSteps { ... }
Traits Galore in our codebase...
xsbt
scalaz
use scala at your own risk
Thanks
for
Listening
@patforna | @mhoefi