Skip to content

Commit

Permalink
Initial implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
rafaelspinto committed Dec 21, 2018
0 parents commit c7a1880
Show file tree
Hide file tree
Showing 23 changed files with 592 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.idea
target
*.iml
13 changes: 13 additions & 0 deletions .travis.yml
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
147 changes: 147 additions & 0 deletions README.md
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
105 changes: 105 additions & 0 deletions pom.xml
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>
6 changes: 6 additions & 0 deletions src/main/scala/workshop/calculator/Calculator.scala
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

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package workshop.login

class InvalidCredentialsException extends Throwable{

}
20 changes: 20 additions & 0 deletions src/main/scala/workshop/login/LoginManager.scala
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
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package workshop.payment

class InsufficientFundsException extends Throwable{

}
14 changes: 14 additions & 0 deletions src/main/scala/workshop/payment/PaymentBroker.scala
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
}
}
8 changes: 8 additions & 0 deletions src/main/scala/workshop/payment/Provider.scala
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
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package workshop.payment

class ProviderNotAvailableException extends Throwable {

}
8 changes: 8 additions & 0 deletions src/main/scala/workshop/payment/Wallet.scala
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
}
5 changes: 5 additions & 0 deletions src/main/scala/workshop/strings/EmptyPasswordException.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package workshop.strings

class EmptyPasswordException extends Throwable{

}
5 changes: 5 additions & 0 deletions src/main/scala/workshop/strings/EmptyStringException.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package workshop.strings

class EmptyStringException extends Throwable {

}
15 changes: 15 additions & 0 deletions src/main/scala/workshop/strings/StringProcessor.scala
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
}
}
Loading

0 comments on commit c7a1880

Please sign in to comment.