-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit c7a1880
Showing
23 changed files
with
592 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
.idea | ||
target | ||
*.iml |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
language: scala | ||
|
||
dist: trusty | ||
|
||
addons: | ||
sonarcloud: | ||
organization: rafaelspinto-github | ||
token: | ||
secure: j8kq5cQDxJn8qc9b88uSk3LRbJp1fLPx01peRQ6HeYM67BJJ1iXtIg11wXcoPAnpHkAL2+AXeZQIwgLvTyuk1Ri8WQpkMsSid95MiShbm7WP6nYie9vkQRW7BUEXzivaqw8kkMQNWH7/rVsB+i1jq3Ind1UcN1jmX4kysd9bgjt4wPfBcY1q8j3jTNSOTMlWTISvagbUV5nQFeGONLS1bOHqhL5jbvzDE+rpZEv8Ij0H95Rhqym1yCqKKEx7+/iWRDjpX/TZWgtWYRjiiR9s796w+p6RAACd310wGOhbuGs415hU+Y7+hoOEL0QuZ8rRf8r6LJJZx6PbJFhi5R2KEtzpCbDURJP0nCbSNkeP7F4WF3JTCKqlA1KmEfoUax6JlxuDUKyRt3rm78l15Gr9vis7jnsI4V4pkQREEGQcGk2pzkwNlRxGioLDB08eJYi2XiZoamSZ9kXbfY022pRZUYQjaFmQ4GGErzt4maIIFY0AmOCsXONztG6mLAVZF6zM4pjNfzJCtcaWr0AJkr4vQY0xY6SlFQPoxoDOyNCA8HFPKdtVxuAPzFAXHK+4SM9TQgvhEmlbOkPepba7k+wE2FpHHqNQZ+sN3sRhx7BBr3DLD3o1ka+858lQHFBGZKP8EzBV42TPZatnmJdr8PXOtIJ2TZluNxbc3lbJjTHz58I= | ||
|
||
script: | ||
- mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent install sonar:sonar | ||
- mvn test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
# TDD Workshop using Scala | ||
|
||
|
||
This workshop is designed to help you start or improve your [Test Driven Development](https://en.wikipedia.org/wiki/Test-driven_development) skills. | ||
|
||
The [examples](#examples) you will see in this workshop are designed to demonstrate the advantages and technicalities of TDD. The intention is to represent real-world scenarios, however sometimes that will not be possible in favour of simplicity. | ||
|
||
|
||
## What is TDD | ||
|
||
[Test Driven Development](https://en.wikipedia.org/wiki/Test-driven_development) or Test First Development is a process that consists of turning the requirements of the software application into specific test cases (acceptance criteria) and then implement the source code. | ||
|
||
This process uses the **red/green/refactor** pattern and consists of the following steps: | ||
|
||
1. Create Test | ||
2. Run Tests (should fail - Red) | ||
3. Write Code | ||
4. Run Tests (should pass - Green) | ||
5. Refactor | ||
|
||
Repeat | ||
|
||
## Table of contents | ||
|
||
* [Quick start](#quick-start) | ||
* [Testing Tools/Frameworks](#testing-toolsframeworks) | ||
* [Naming conventions](#naming-conventions) | ||
* [AAA Pattern](#aaa-pattern) | ||
* [Mocks & Stubs](#mocks--stubs) | ||
* [Instant Feedback Tools](#instant-feedback-tools) | ||
* [Examples](#examples) | ||
|
||
## Quick start | ||
|
||
Prerequisites | ||
|
||
* You have an IDE or a text editor (e.g.: [IntelliJ IDEA](https://www.jetbrains.com/idea/download)) | ||
* You have [Maven](https://maven.apache.org/) installed | ||
|
||
|
||
## Testing Tools/Frameworks | ||
|
||
We will be using a few tools/frameworks to facilitate our job. | ||
|
||
* [JUnit](https://junit.org/junit4/) - Unit Testing Framework | ||
* [ScalaMock](https://scalamock.org/) - Mocking Framework for Scala | ||
* [ScalaTest](http://www.scalatest.org/) - Unit Testing Framework for Scala. | ||
|
||
## Naming conventions | ||
|
||
Tests serve 3 purposes: | ||
|
||
* [Acceptance Criteria](https://en.wikipedia.org/wiki/Acceptance_testing) - Ensures developed code meets specification | ||
* [Regression Testing](https://en.wikipedia.org/wiki/Regression_testing) - Ensures new changes don't impact previous developed code | ||
* **Documentation** - Demonstrates how the application behaves | ||
|
||
To achieve proper documentation, a good starting point is to create naming conventions for the tests. | ||
|
||
You can define your own conventions keeping in mind that the test methods should clearly identify: | ||
|
||
* What is being tested | ||
* What is the Scenario (Input) | ||
* What should be the outcome (Output) | ||
|
||
Example with a traditional approach (simple JUnit): | ||
|
||
```scala | ||
test("sum: if both numbers are Positive returns positive number") { | ||
... | ||
} | ||
``` | ||
|
||
### AAA Pattern | ||
|
||
Tests typically follow the AAA pattern: | ||
|
||
* **A**rrange - Setup of the Scenario, e.g.: creating the input data | ||
* **A**ct - Executing the action/method | ||
* **A**ssert - Validation of the outcome | ||
|
||
Example: | ||
|
||
|
||
```scala | ||
test("sum: if both numbers are Positive returns a positive number") { | ||
// Arrange | ||
val a = 10 | ||
val b = 20 | ||
val calc = new Calculator() | ||
|
||
// Act | ||
val result = calc.sum(a, b) | ||
|
||
//Assert | ||
assert(result > 0) | ||
} | ||
``` | ||
|
||
## Mocks & Stubs | ||
|
||
[Mocks](https://en.wikipedia.org/wiki/Mock_object) and [Stubs](https://en.wikipedia.org/wiki/Method_stub) are used to facilitate testing by solving the problem of dependencies. | ||
|
||
When the code you are implementing has a dependency, using this technique, you create a fake object that emulates that dependency. If you are required to define specific return values to emulate a certain scenario then you'll need to use a **stub** otherwise you'll simply use a **mock**. | ||
|
||
|
||
Example: | ||
|
||
|
||
* **Mock** | ||
|
||
```scala | ||
var wallet = mock[Wallet] | ||
var provider = mock[Provider] | ||
var broker = new PaymentBroker(wallet, provider) | ||
|
||
``` | ||
|
||
* **Stub** | ||
```scala | ||
var wallet = stub[Wallet] | ||
var provider = stub[Provider] | ||
var broker = new PaymentBroker(wallet, provider) | ||
(wallet.getBalance _).when().returns(balance) | ||
(provider.isAvailable _).when().returns(true) | ||
``` | ||
|
||
## Instant Feedback Tools | ||
|
||
Feedback is one of the most important things in the development world, the sooner you get it the better. | ||
|
||
Typically most of the feedback comes from the user/client of your software, but you should be getting it before you ship it. | ||
|
||
There are plenty of tools out there that can help you with this. In this workshop we will be using the following: | ||
|
||
* **Automation Server** - Allows you to automate the test execution ([Continuous Integration](https://www.thoughtworks.com/continuous-integration)) and other routines associated with it ([Continuous Delivery](https://martinfowler.com/bliki/ContinuousDelivery.html)/[Continuous Deployment](https://www.agilealliance.org/glossary/continuous-deployment/)). In this particular case we are using [Travis CI](https://travis-ci.org/). | ||
|
||
* **Static Code Analyzer** - Allows you to continuously inspect the quality of the code by detecting issues and providing suggestions to solve them. In this project we are using [SonarCloud](http://sonarcloud.io). | ||
|
||
* **Kanban Board** - Allows you to track your project's work with a workflow visualization tool. | ||
|
||
|
||
## Examples | ||
|
||
* [Calculator](/src/test/scala/workshop/calculator) - Simple example to get you started | ||
* [LoginManager](/src/test/scala/workshop/login) - Working with exceptions | ||
* [StringProcessor](/src/test/scala/workshop/strings) - Working with data providers | ||
* [PaymentBroker](/src/test/scala/workshop/payment) - Working with dependencies, mocks and stubs |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
<groupId>com.rafaelspinto</groupId> | ||
<artifactId>workshop-tdd-scala</artifactId> | ||
<version>1.0-SNAPSHOT</version> | ||
<name>${project.artifactId}</name> | ||
<inceptionYear>2018</inceptionYear> | ||
|
||
<properties> | ||
<maven.compiler.source>1.8</maven.compiler.source> | ||
<maven.compiler.target>1.8</maven.compiler.target> | ||
<encoding>UTF-8</encoding> | ||
<scala.version>2.12.6</scala.version> | ||
<scala.compat.version>2.12</scala.compat.version> | ||
</properties> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>org.scala-lang</groupId> | ||
<artifactId>scala-library</artifactId> | ||
<version>${scala.version}</version> | ||
</dependency> | ||
|
||
<!-- Test --> | ||
<dependency> | ||
<groupId>junit</groupId> | ||
<artifactId>junit</artifactId> | ||
<version>4.12</version> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.scalatest</groupId> | ||
<artifactId>scalatest_${scala.compat.version}</artifactId> | ||
<version>3.0.5</version> | ||
<scope>test</scope> | ||
</dependency> | ||
<!-- https://mvnrepository.com/artifact/org.scalamock/scalamock-core --> | ||
<dependency> | ||
<groupId>org.scalamock</groupId> | ||
<artifactId>scalamock-core_${scala.compat.version}</artifactId> | ||
<version>3.6.0</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.scalamock</groupId> | ||
<artifactId>scalamock-scalatest-support_${scala.compat.version}</artifactId> | ||
<version>3.6.0</version> | ||
<scope>test</scope> | ||
</dependency> | ||
</dependencies> | ||
<build> | ||
<sourceDirectory>src/main/scala</sourceDirectory> | ||
<testSourceDirectory>src/test/scala</testSourceDirectory> | ||
<plugins> | ||
<plugin> | ||
<!-- see http://davidb.github.com/scala-maven-plugin --> | ||
<groupId>net.alchim31.maven</groupId> | ||
<artifactId>scala-maven-plugin</artifactId> | ||
<version>3.4.4</version> | ||
<executions> | ||
<execution> | ||
<goals> | ||
<goal>compile</goal> | ||
<goal>testCompile</goal> | ||
</goals> | ||
<configuration> | ||
<args> | ||
<arg>-dependencyfile</arg> | ||
<arg>${project.build.directory}/.scala_dependencies</arg> | ||
</args> | ||
</configuration> | ||
</execution> | ||
</executions> | ||
</plugin> | ||
<!-- disable surefire --> | ||
<plugin> | ||
<groupId>org.apache.maven.plugins</groupId> | ||
<artifactId>maven-surefire-plugin</artifactId> | ||
<version>2.21.0</version> | ||
<configuration> | ||
<!-- Tests will be run with scalatest-maven-plugin instead --> | ||
<skipTests>true</skipTests> | ||
</configuration> | ||
</plugin> | ||
|
||
<plugin> | ||
<groupId>org.scalatest</groupId> | ||
<artifactId>scalatest-maven-plugin</artifactId> | ||
<version>2.0.0</version> | ||
<configuration> | ||
<reportsDirectory>${project.build.directory}/surefire-reports</reportsDirectory> | ||
<junitxml>.</junitxml> | ||
<filereports>TestSuiteReport.txt</filereports> | ||
</configuration> | ||
<executions> | ||
<execution> | ||
<id>test</id> | ||
<goals> | ||
<goal>test</goal> | ||
</goals> | ||
</execution> | ||
</executions> | ||
</plugin> | ||
</plugins> | ||
</build> | ||
</project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package workshop.calculator | ||
|
||
class Calculator() { | ||
def sum(a: Int, b: Int) = a + b | ||
|
||
} |
5 changes: 5 additions & 0 deletions
5
src/main/scala/workshop/login/InvalidCredentialsException.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package workshop.login | ||
|
||
class InvalidCredentialsException extends Throwable{ | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package workshop.login | ||
|
||
import workshop.strings.EmptyPasswordException | ||
|
||
import scala.collection.immutable.HashMap | ||
|
||
|
||
class LoginManager( | ||
val userRepo: HashMap[String, String] | ||
) { | ||
def login(username: String, password: String): Boolean = { | ||
if (password.isEmpty) { | ||
throw new EmptyPasswordException | ||
} | ||
if (!userRepo.contains(username) || userRepo.get(username) != Some(password)) { | ||
throw new InvalidCredentialsException | ||
} | ||
true | ||
} | ||
} |
5 changes: 5 additions & 0 deletions
5
src/main/scala/workshop/payment/InsufficientFundsException.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package workshop.payment | ||
|
||
class InsufficientFundsException extends Throwable{ | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package workshop.payment | ||
|
||
class PaymentBroker(wallet: Wallet, provider: Provider) { | ||
def pay(amount: Int): Boolean = { | ||
if (wallet.getBalance < amount) { | ||
throw new InsufficientFundsException | ||
} | ||
|
||
if (!provider.isAvailable) { | ||
throw new ProviderNotAvailableException | ||
} | ||
true | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package workshop.payment | ||
|
||
trait Provider { | ||
|
||
def deposit(id: Int, amount: Int): Boolean | ||
|
||
def isAvailable: Boolean | ||
} |
5 changes: 5 additions & 0 deletions
5
src/main/scala/workshop/payment/ProviderNotAvailableException.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package workshop.payment | ||
|
||
class ProviderNotAvailableException extends Throwable { | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package workshop.payment | ||
|
||
trait Wallet { | ||
|
||
def getId(): Int = 1234 | ||
|
||
def getBalance: Int | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package workshop.strings | ||
|
||
class EmptyPasswordException extends Throwable{ | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package workshop.strings | ||
|
||
class EmptyStringException extends Throwable { | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package workshop.strings | ||
|
||
class StringProcessor() { | ||
def countVowels(str: String) = { | ||
if (str.isEmpty) { | ||
throw new EmptyStringException | ||
} | ||
var count = 0 | ||
val vowels = "aeiouAEIOU" | ||
for (char <- str.toCharArray) { | ||
if (vowels.contains(char)) count += 1 | ||
} | ||
count | ||
} | ||
} |
Oops, something went wrong.