+++ /dev/null
-Sandro Mancuso: testing a refactoring legacy code
-
-Rules for refactoring legacy code.
-
-1. You may not change production code if not covered by tests. Before refactoring some legacy code we must write a Unit Test for it.
-
- But some times for writing the test we must make modifications in the legacy code. If such modifications are required: just automated refactoring (via IDES) are allowed, if needed to write the test.
-
-
-Steps:
-
-1. Start testing the legacy code following the shortest branch.
-
-2. In Unit Test we should not invoke other classes but the one being under test (the other classes should be stub, mocke, etc, etc)
-
-3. When the legacy code is covered by a unit test we may refactor it (never before) We have to refactor our legacy code from the deepest to the shortest branch.
-
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<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/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <groupId>de.example.tdd</groupId>
- <artifactId>tdd-by-example</artifactId>
- <packaging>jar</packaging>
- <version>1.0-SNAPSHOT</version>
- <name>tdd-by-example</name>
- <url>http://gumartinm.name</url>
- <description>TDD by example</description>
- <organization>
- <name>Gustavo Martin Morcuende</name>
- <url>http://www.gumartinm.name</url>
- </organization>
- <scm>
- <developerConnection>scm:git:http://git.gumartinm.name/TDDbyExample</developerConnection>
- <url>http://git.gumartinm.name/TDDbyExample</url>
- </scm>
- <properties>
- <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
- </properties>
- <dependencies>
- <!--
- **************************************************************************************************************************
- 1. LOGBACK: SLF4J NATIVE IMPLEMENTATION. See: http://logback.qos.ch/
- **************************************************************************************************************************
- -->
- <!--
- 1/1 Required dependency for native implementation (nothing else is required)
-
- Native implementation. There are also SLF4J bindings external to the SLF4J project, e.g. logback which implements
- SLF4Jnatively. Logback's ch.qos.logback.classic.Logger class is a direct implementation of SLF4J's org.slf4j.Logger
- interface. Thus, using SLF4J in conjunction with logback involves strictly zero memory and computational overhead.
- see: http://www.slf4j.org/manual.html
- -->
- <dependency>
- <groupId>ch.qos.logback</groupId>
- <artifactId>logback-classic</artifactId>
- <version>1.1.2</version>
- </dependency>
- <!--
- **************************************************************************************************************************
- 2. SLF4J AND LOG4J 2. See: http://logging.apache.org/log4j/2.x/
- **************************************************************************************************************************
- -->
-
- <!--
- 1/2 Required dependency for log4j 2 with slf4j: binding
- <dependency>
- <groupId>org.apache.logging.log4j</groupId>
- <artifactId>log4j-slf4j-impl</artifactId>
- <version>2.0-rc1</version>
- </dependency>
- -->
- <!--
- 2/2 Required dependency for log4j 2 with slf4j: log4j 2 maven plugin
- <dependency>
- <groupId>org.apache.logging.log4j</groupId>
- <artifactId>log4j-core</artifactId>
- <version>2.0-rc1</version>
- </dependency>
- -->
- <dependency>
- <groupId>javax.inject</groupId>
- <artifactId>javax.inject</artifactId>
- <version>1</version>
- </dependency>
-
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <version>4.12-beta-1</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.mockito</groupId>
- <artifactId>mockito-core</artifactId>
- <version>2.0.31-beta</version>
- <scope>test</scope>
- </dependency>
- </dependencies>
- <build>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-compiler-plugin</artifactId>
- <version>3.1</version>
- <configuration>
- <source>1.8</source>
- <target>1.8</target>
- <!--
- <source>1.6</source>
- <target>1.6</target>
- -->
- <encoding>${project.build.sourceEncoding}</encoding>
- <compilerArgument>-Xlint:deprecation</compilerArgument>
- </configuration>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-resources-plugin</artifactId>
- <version>2.6</version>
- <configuration>
- <encoding>${project.build.sourceEncoding}</encoding>
- </configuration>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-surefire-plugin</artifactId>
- <version>2.17</version>
- <!--
- Trying autodetection, see: http://maven.apache.org/surefire/maven-surefire-plugin/examples/junit.html
- <dependencies>
- <dependency>
- <groupId>org.apache.maven.surefire</groupId>
- <artifactId>surefire-junit47</artifactId>
- <version>2.17</version>
- </dependency>
- </dependencies>
- -->
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-jar-plugin</artifactId>
- <version>2.3.1</version>
- <configuration>
- <archive>
- <manifestEntries>
- <Specification-Title>${project.description}</Specification-Title>
- <Specification-Version>${project.version}</Specification-Version>
- <Specification-Vendor>${project.organization.name}</Specification-Vendor>
- <Implementation-Title>${project.description}</Implementation-Title>
- <Implementation-Version>${project.version}</Implementation-Version>
- <Implementation-Vendor>${project.organization.name}</Implementation-Vendor>
- </manifestEntries>
- </archive>
- </configuration>
- </plugin>
- </plugins>
- </build>
- <reporting>
- <plugins>
- <plugin>
- <!--
- <groupId>org.jacoco</groupId>
- <artifactId>jacoco</artifactId>
- <version>0.7.2.201409121644</version>
- -->
- <groupId>org.codehaus.mojo</groupId>
- <artifactId>cobertura-maven-plugin</artifactId>
- <version>2.6</version>
- </plugin>
- </plugins>
- </reporting>
-</project>
--- /dev/null
+Sandro Mancuso: testing a refactoring legacy code
+
+Rules for refactoring legacy code.
+
+1. You may not change production code if not covered by tests. Before refactoring some legacy code we must write a Unit Test for it.
+
+ But some times for writing the test we must make modifications in the legacy code. If such modifications are required: just automated refactoring (via IDES) are allowed, if needed to write the test.
+
+
+Steps:
+
+1. Start testing the legacy code following the shortest branch.
+
+2. In Unit Test we should not invoke other classes but the one being under test (the other classes should be stub, mocke, etc, etc)
+
+3. When the legacy code is covered by a unit test we may refactor it (never before) We have to refactor our legacy code from the deepest to the shortest branch.
+
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<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/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.craftedsw</groupId>
+ <artifactId>sandromancuso</artifactId>
+ <packaging>jar</packaging>
+ <version>1.0-SNAPSHOT</version>
+ <name>tdd-by-example</name>
+ <url>http://gumartinm.name</url>
+ <description>TDD by example</description>
+ <organization>
+ <name>Gustavo Martin Morcuende</name>
+ <url>http://www.gumartinm.name</url>
+ </organization>
+ <scm>
+ <developerConnection>scm:git:http://git.gumartinm.name/TDDbyExample</developerConnection>
+ <url>http://git.gumartinm.name/TDDbyExample</url>
+ </scm>
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ </properties>
+ <dependencies>
+ <!--
+ **************************************************************************************************************************
+ 1. LOGBACK: SLF4J NATIVE IMPLEMENTATION. See: http://logback.qos.ch/
+ **************************************************************************************************************************
+ -->
+ <!--
+ 1/1 Required dependency for native implementation (nothing else is required)
+
+ Native implementation. There are also SLF4J bindings external to the SLF4J project, e.g. logback which implements
+ SLF4Jnatively. Logback's ch.qos.logback.classic.Logger class is a direct implementation of SLF4J's org.slf4j.Logger
+ interface. Thus, using SLF4J in conjunction with logback involves strictly zero memory and computational overhead.
+ see: http://www.slf4j.org/manual.html
+ -->
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-classic</artifactId>
+ <version>1.1.2</version>
+ </dependency>
+ <!--
+ **************************************************************************************************************************
+ 2. SLF4J AND LOG4J 2. See: http://logging.apache.org/log4j/2.x/
+ **************************************************************************************************************************
+ -->
+
+ <!--
+ 1/2 Required dependency for log4j 2 with slf4j: binding
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-slf4j-impl</artifactId>
+ <version>2.0-rc1</version>
+ </dependency>
+ -->
+ <!--
+ 2/2 Required dependency for log4j 2 with slf4j: log4j 2 maven plugin
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-core</artifactId>
+ <version>2.0-rc1</version>
+ </dependency>
+ -->
+ <dependency>
+ <groupId>javax.inject</groupId>
+ <artifactId>javax.inject</artifactId>
+ <version>1</version>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.12-beta-1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>2.0.31-beta</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>3.1</version>
+ <configuration>
+ <source>1.8</source>
+ <target>1.8</target>
+ <!--
+ <source>1.6</source>
+ <target>1.6</target>
+ -->
+ <encoding>${project.build.sourceEncoding}</encoding>
+ <compilerArgument>-Xlint:deprecation</compilerArgument>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-resources-plugin</artifactId>
+ <version>2.6</version>
+ <configuration>
+ <encoding>${project.build.sourceEncoding}</encoding>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <version>2.17</version>
+ <!--
+ Trying autodetection, see: http://maven.apache.org/surefire/maven-surefire-plugin/examples/junit.html
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.maven.surefire</groupId>
+ <artifactId>surefire-junit47</artifactId>
+ <version>2.17</version>
+ </dependency>
+ </dependencies>
+ -->
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <version>2.3.1</version>
+ <configuration>
+ <archive>
+ <manifestEntries>
+ <Specification-Title>${project.description}</Specification-Title>
+ <Specification-Version>${project.version}</Specification-Version>
+ <Specification-Vendor>${project.organization.name}</Specification-Vendor>
+ <Implementation-Title>${project.description}</Implementation-Title>
+ <Implementation-Version>${project.version}</Implementation-Version>
+ <Implementation-Vendor>${project.organization.name}</Implementation-Vendor>
+ </manifestEntries>
+ </archive>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <reporting>
+ <plugins>
+ <plugin>
+ <!--
+ <groupId>org.jacoco</groupId>
+ <artifactId>jacoco</artifactId>
+ <version>0.7.2.201409121644</version>
+ -->
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>cobertura-maven-plugin</artifactId>
+ <version>2.6</version>
+ </plugin>
+ </plugins>
+ </reporting>
+</project>
--- /dev/null
+package org.craftedsw.tripservicekata;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.craftedsw.tripservicekata.exception.UserNotLoggedInException;
+import org.craftedsw.tripservicekata.trip.Trip;
+import org.craftedsw.tripservicekata.trip.TripDAO;
+import org.craftedsw.tripservicekata.user.User;
+import org.craftedsw.tripservicekata.user.UserSession;
+
+public class TripService_Original {
+
+ public List<Trip> getTripsByUser(User user) throws UserNotLoggedInException {
+ List<Trip> tripList = new ArrayList<Trip>();
+ User loggedUser = UserSession.getInstance().getLoggedUser();
+ boolean isFriend = false;
+ if (loggedUser != null) {
+ for (User friend : user.getFriends()) {
+ if (friend.equals(loggedUser)) {
+ isFriend = true;
+ break;
+ }
+ }
+ if (isFriend) {
+ tripList = TripDAO.findTripsByUser(user);
+ }
+ return tripList;
+ } else {
+ throw new UserNotLoggedInException();
+ }
+ }
+
+}
--- /dev/null
+package org.craftedsw.tripservicekata;
+
+import org.craftedsw.tripservicekata.trip.Trip;
+import org.craftedsw.tripservicekata.user.User;
+
+public class UserBuilder {
+ private User[] friends = new User[]{};
+ private Trip[] trips = new Trip[]{};
+
+ public static UserBuilder aUser() {
+ return new UserBuilder();
+ }
+
+ public UserBuilder withTrips(Trip...trips) {
+ this.trips = trips;
+ return this;
+ }
+
+ public UserBuilder friendsWith(User...friends) {
+ this.friends = friends;
+ return this;
+ }
+
+ public User build() {
+ User user = new User();
+ addTripsTo(user);
+ addFriendsTo(user);
+ return user;
+ }
+
+ private void addFriendsTo(User user) {
+ for (User friend : friends) {
+ user.addFriend(friend);
+ }
+ }
+
+ private void addTripsTo(User user) {
+ for (Trip trip : trips) {
+ user.addTrip(trip);
+ }
+ }
+}
--- /dev/null
+package org.craftedsw.tripservicekata.exception;
+
+public class CollaboratorCallException extends RuntimeException {
+
+ private static final long serialVersionUID = -4584041339906109902L;
+
+ public CollaboratorCallException() {
+ super();
+ }
+
+ public CollaboratorCallException(String message,
+ Throwable cause) {
+ super(message, cause);
+ }
+
+ public CollaboratorCallException(String message) {
+ super(message);
+ }
+
+ public CollaboratorCallException(Throwable cause) {
+ super(cause);
+ }
+
+
+}
--- /dev/null
+package org.craftedsw.tripservicekata.exception;
+
+public class UserNotLoggedInException extends RuntimeException {
+
+ private static final long serialVersionUID = 8959479918185637340L;
+
+}
--- /dev/null
+package org.craftedsw.tripservicekata.trip;
+
+public class Trip {
+
+}
--- /dev/null
+package org.craftedsw.tripservicekata.trip;
+
+import java.util.List;
+
+import org.craftedsw.tripservicekata.exception.CollaboratorCallException;
+import org.craftedsw.tripservicekata.user.User;
+
+public class TripDAO {
+
+ public static List<Trip> findTripsByUser(User user) {
+ throw new CollaboratorCallException(
+ "TripDAO should not be invoked on an unit test.");
+ }
+
+ // At the end and by means of many code refactoring our app
+ // will end up using the instance method. In the meanwhile we
+ // will not be able to remove the static method but at least
+ // we are offering something that can be tested.
+ // Be careful when refactoring this code because in real life
+ // findTripsByUser for sure will be used in many places but again
+ // in the meanwhile with this code we can write unit tests (we
+ // always need to write a test of our legacy code before refactoring it)
+ public List<Trip> tripsBy(User user) {
+ return TripDAO.findTripsByUser(user);
+ }
+
+}
\ No newline at end of file
--- /dev/null
+package org.craftedsw.tripservicekata.trip;\r
+\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+\r
+import javax.inject.Inject;\r
+\r
+import org.craftedsw.tripservicekata.exception.UserNotLoggedInException;\r
+import org.craftedsw.tripservicekata.user.User;\r
+\r
+public class TripService {\r
+ \r
+ private final TripDAO tripDAO;\r
+\r
+ @Inject\r
+ public TripService(TripDAO tripDAO) {\r
+ this.tripDAO = tripDAO;\r
+ }\r
+\r
+ public List<Trip> getTripsByUser(User user, User loggedInUser) throws UserNotLoggedInException {\r
+ validate(loggedInUser);\r
+ \r
+ return user.isFriendsWith(loggedInUser)\r
+ ? tripsBy(user)\r
+ : noTrips();\r
+ }\r
+\r
+ private void validate(User loggedInUser) {\r
+ if (loggedInUser == null) {\r
+ throw new UserNotLoggedInException();\r
+ }\r
+ }\r
+\r
+ private ArrayList<Trip> noTrips() {\r
+ return new ArrayList<>();\r
+ }\r
+\r
+ private List<Trip> tripsBy(User user) {\r
+ return tripDAO.tripsBy(user);\r
+ }\r
+}\r
--- /dev/null
+package org.craftedsw.tripservicekata.user;\r
+\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+\r
+import org.craftedsw.tripservicekata.trip.Trip;\r
+\r
+public class User {\r
+\r
+ private List<Trip> trips = new ArrayList<Trip>();\r
+ private List<User> friends = new ArrayList<User>();\r
+ \r
+ public List<User> getFriends() {\r
+ return friends;\r
+ }\r
+ \r
+ public void addFriend(User user) {\r
+ friends.add(user);\r
+ }\r
+\r
+ public void addTrip(Trip trip) {\r
+ trips.add(trip);\r
+ }\r
+ \r
+ public List<Trip> trips() {\r
+ return trips;\r
+ }\r
+\r
+ public boolean isFriendsWith(User anotherUser) {\r
+ return friends.contains(anotherUser);\r
+ }\r
+\r
+}\r
--- /dev/null
+package org.craftedsw.tripservicekata.user;
+
+import org.craftedsw.tripservicekata.exception.CollaboratorCallException;
+
+public class UserSession {
+
+ private static final UserSession userSession = new UserSession();
+
+ private UserSession() {
+ }
+
+ public static UserSession getInstance() {
+ return userSession;
+ }
+
+ public User getLoggedUser() {
+ throw new CollaboratorCallException(
+ "UserSession.getLoggedUser() should not be called in an unit test");
+ }
+
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * name: The name of the configuration.
+ * dest: Either "err", which will send output to stderr, or a file path or URL.
+ * status: The level of internal Log4j events that should be logged to the console. Valid values for this attribute are "trace",
+ "debug", "info", "warn", "error" and "fatal". Log4j will log details about initialization, rollover and other internal
+ actions to the status logger.
+ * strict: Enables the use of the strict XML format. Not supported in JSON configurations.
+ * monitorInterval: If the monitorInterval attribute is specified on the configuration element and is set to a non-zero value
+ then the file will be checked the next time a log event is evaluated and/or logged and the monitorInterval has elapsed
+ since the last check.
+ * packages: A comma separated list of package names to search for plugins. Plugins are only loaded once per classloader so
+ changing this value may not have any effect upon reconfiguration.
+-->
+<Configuration name="XMLConfigTest" dest="err" status="trace" strict="true" monitorInterval="30" packages="org.apache.logging.log4j.test">
+ <!--
+ Log4j 2 supports the ability to specify tokens in the configuration as references to properties defined elsewhere. Some of
+ these properties will be resolved when the configuration file is interpreted while others may be passed to components
+ where they will be evaluated at runtime.
+ -->
+ <Properties>
+ <Property name="filename">target/fileappender.log</Property>
+ <Property name="filerandomname">target/filerandomappender.log</Property>
+ </Properties>
+
+ <!--
+ Filters may be configured in one of four locations:
+
+ Context-wide Filters are configured directly in the configuration. Events that are rejected by these filters
+ will not be passed to loggers for further processing. Once an event has been accepted by a Context-wide filter it will not be evaluated by any other Context-wide Filters nor will the Logger's Level be used to filter the event. The event will be evaluated by Logger and Appender Filters however.
+ Logger Filters are configured on a specified Logger. These are evaluated after the Context-wide Filters and the Log Level for the Logger. Events that are rejected by these filters will be discarded and the event will not be passed to a parent Logger regardless of the additivity setting.
+ Appender Filters are used to determine if a specific Appender should handle the formatting and publication of the event.
+ Appender Reference Filters are used to determine if a Logger should route the event to an appender.
+
+ <Filter type="ThresholdFilter" level="debug"/>
+ -->
+
+ <Appenders>
+ <Appender type="Console" name="STDOUT">
+ <!--
+ <ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/>
+ -->
+ <Filter type="ThresholdFilter" level="trace"/>
+ <!--
+ <Layout type="PatternLayout" pattern="%d{ABSOLUTE} %5p %c{1}:%L %m%n"/>
+ -->
+ <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx - %X{id} %X{name} %X{surname}%n"/>
+ <!--
+ <Filters>
+ See: FlowTracingExample class in this project.
+ <Filter type="MarkerFilter" marker="FLOW" onMatch="ENTER" onMismatch="NEUTRAL"/>
+ <Filter type="MarkerFilter" marker="EXCEPTION" onMatch="CATCHING" onMismatch="ACCEPT"/>
+
+ See: MarkersExample class in this project.
+ ¿Cómo usar esto cuando hago logger.debug(QUERY_MARKER, "SELECT * FROM {}", table);?
+ ¿Sería marker="DEBUG"?
+ <Filter type="MarkerFilter" marker="FLOW" onMatch="SQL_UPDATE" onMismatch="NEUTRAL"/>
+ <Filter type="MarkerFilter" marker="EXCEPTION" onMatch="SQL_QUERY" onMismatch="ACCEPT"/>
+ </Filters>
+ -->
+ </Appender>
+ <Appender type="Console" name="FLOW">
+ <Layout type="PatternLayout" pattern="%C{1}.%M %m %ex%n"/><!-- class and line number -->
+ <Filters>
+ <Filter type="MarkerFilter" marker="FLOW" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
+ <Filter type="MarkerFilter" marker="EXCEPTION" onMatch="ACCEPT" onMismatch="DENY"/>
+ </Filters>
+ </Appender>
+ <!--
+ ¿Puedo mezclar bufferedIO=true con immediateFlush=true?
+ LOG4J 1.2: bufferedIO="false" immediateFlush="true" (es como se ejecuta actualmente la N2A)
+ JUGAR CON ESOS 2 VALORES A VER QUE TAL... :/
+ -->
+ <Appender type="File" name="FileAppender" fileName="${filename}" bufferedIO="false" immediateFlush="true" append="true">
+ <Layout type="PatternLayout">
+ <!--
+ <Pattern>%d %p %C{1.} [%t] %m%n</Pattern>
+ -->
+ <Pattern>"%d{yyyyMMddHHmmssSSS} - %-5p - [%t] - %m%n"</Pattern>
+ <Charset>UTF-8</Charset>
+ </Layout>
+ </Appender>
+ <!--
+ * Asynchronous Loggers are a new addition to Log4j 2. Their aim is to return from the call to Logger.log to the application
+ as soon as possible. You can choose between making all Loggers asynchronous or using a mixture of synchronous and asynchronous
+ Loggers. Making all Loggers asynchronous will give the best performance, while mixing gives you more flexibility.
+ * LMAX Disruptor technology. Asynchronous Loggers internally use the Disruptor, a lock-free inter-thread communication library,
+ instead of queues, resulting in higher throughput and lower latency.
+ * Asynchronous Appenders already existed in Log4j 1.x, but have been enhanced to flush to disk at the end of a batch
+ (when the queue is empty). This produces the same result as configuring "immediateFlush=true", that is, all received log events
+ are always available on disk, but is more efficient because it does not need to touch the disk on each and every log event.
+ (Async Appenders use ArrayBlockingQueue internally and do not need the disruptor jar on the classpath.)
+ * (For synchronous and asynchronous use) Random Access File Appenders are an alternative to Buffered File Appenders. Under the hood,
+ these new appenders use a ByteBuffer + RandomAccessFile instead of a BufferedOutputStream. In our testing this was about 20-200%
+ faster. These appenders can also be used with synchronous loggers and will give the same performance benefits. Random Access
+ File Appenders do not need the disruptor jar on the classpath.
+ -->
+ <!-- El random access file siempre es con buffer!!!
+ When set to true - the default, each write will be followed by a flush. This will guarantee the data is written
+ to disk but could impact performance.
+
+ Flushing after every write is only useful when using this appender with synchronous loggers. Asynchronous loggers
+ and appenders will automatically flush at the end of a batch of events, even if immediateFlush is set to false.
+ This also guarantees the data is written to disk but is more efficient.
+ -->
+ <Appender type="RandomAccessFile" name="RandomAccessFileAppender" fileName="${filerandomname}" immediateFlush="true" append="true">
+ <Layout type="PatternLayout">
+ <Pattern>"%d{yyyyMMddHHmmssSSS} - %-5p - [%t] - %m%n"</Pattern>
+ <Charset>UTF-8</Charset>
+ </Layout>
+ </Appender>
+ <!--
+ El AsyncAppender siempre es bufferado y hace siempre un immediateFlush ¿independientemente del appender ref? SÍ, mira:
+
+ Mejor usar includeLocation=false proque el AsyncAppender siempre debe hacer la localización del log (nombre de clase, método y línea)
+ independientemente de si luego se va a loguear o no (porque haya filtros por ejemplo) Esto es así porque el asyncappender
+ debe generar toda la info necesaria antes de pasar esta info a la cola. Una vez en la cola asíncrona esa info ya no puede
+ ser generada. Por tanto, aunque ese log finalmente no se use, siempre se pone en la cola asíncrona con todos sus datos.
+
+ Extracting location is an expensive operation (it can make logging 5 - 20 times slower). To improve performance, location
+ is not included by default when adding a log event to the queue. You can change this by setting includeLocation="true".
+ Para extraer la localización al parecer hay que recorrer el stacktrace por eso es una operacion pesada.
+ -->
+ <Appender type="Async" name="AsyncAppender" blocking="true" bufferSize="128" errorRef="FileAppender" ignoreExceptions="false" includeLocation="false">
+ <!--
+ Probar las dos configuraciones
+ <AppenderRef ref="RandomAccessFileAppender"/>
+ -->
+ <AppenderRef ref="FileAppender"/>
+ </Appender>
+
+ </Appenders>
+
+ <Loggers>
+ <!--
+ <Logger name="de.loggers.test" level="debug" additivity="false">
+ <Filter type="ThreadContextMapFilter">
+ <KeyValuePair key="test" value="123"/>
+ </Filter>
+ <AppenderRef ref="STDOUT"/>
+ </Logger>
+ -->
+
+ <!--
+ <Logger name="de.loggers.test" level="debug" additivity="false">
+ <AppenderRef ref="File"/>
+ </Logger>
+ -->
+
+ <Logger name="de.loggers.test.simple" level="TRACE">
+ <!--
+ * Because THIS logger has additivity="true" by default THIS logger will use the same appender as its parent's logger,
+ which in this case is the logger Root.
+ * Because THIS logger has additivity="true" by default if we use this AppenderRef we will see for
+ classes in de.loggers.test.simple the same messages twice. One from this logger and one from its parent's logger,
+ which in this case is the logger Root.
+ <AppenderRef ref="STDOUT"/>
+ -->
+ </Logger>
+ <Logger name="de.loggers.test.formatter" level="TRACE" additivity="false">
+ <!--
+ * Because THIS logger has additivity="false" THIS logger will not use the same appender as its parent's logger.
+ -->
+ <AppenderRef ref="STDOUT"/>
+ </Logger>
+ <!--
+ By default, location is not passed to the I/O thread by asynchronous loggers. If one of your layouts or custom
+ filters needs location information, you need to set "includeLocation=true" in the configuration of all relevant loggers,
+ including the root logger.
+ -->
+
+
+
+ <!--
+ * Asynchronous Loggers are a new addition to Log4j 2. Their aim is to return from the call to Logger.log to the application
+ as soon as possible. You can choose between making all Loggers asynchronous or using a mixture of synchronous and asynchronous
+ Loggers. Making all Loggers asynchronous will give the best performance, while mixing gives you more flexibility.
+ * LMAX Disruptor technology. Asynchronous Loggers internally use the Disruptor, a lock-free inter-thread communication library,
+ instead of queues, resulting in higher throughput and lower latency.
+ * Asynchronous Appenders already existed in Log4j 1.x, but have been enhanced to flush to disk at the end of a batch
+ (when the queue is empty). This produces the same result as configuring "immediateFlush=true", that is, all received log events
+ are always available on disk, but is more efficient because it does not need to touch the disk on each and every log event.
+ (Async Appenders use ArrayBlockingQueue internally and do not need the disruptor jar on the classpath.)
+ * (For synchronous and asynchronous use) Random Access File Appenders are an alternative to Buffered File Appenders. Under the hood,
+ these new appenders use a ByteBuffer + RandomAccessFile instead of a BufferedOutputStream. In our testing this was about 20-200%
+ faster. These appenders can also be used with synchronous loggers and will give the same performance benefits. Random Access
+ File Appenders do not need the disruptor jar on the classpath.
+ -->
+ <AsyncLogger name="de.loggers.test.loggernames" level="TRACE" includeLocation="false">
+ <AppenderRef ref="RandomAccessFileAppender"/>
+ </AsyncLogger>
+ <Root level="TRACE">
+ <AppenderRef ref="STDOUT"/>
+ </Root>
+ </Loggers>
+
+</Configuration>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- For assistance related to logback-translator or configuration -->
+<!-- files in general, please contact the logback user mailing list -->
+<!-- at http://www.qos.ch/mailman/listinfo/logback-user -->
+<!-- -->
+<!-- For professional support please see -->
+<!-- http://www.qos.ch/shop/products/professionalSupport -->
+<!-- -->
+<configuration>
+ <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
+ <encoder>
+ <pattern>%d{yyyyMMddHHmmssSSS} - %-5p - [%t] - [%C] - %m%n</pattern>
+ </encoder>
+ </appender>
+ <root level="INFO">
+ <appender-ref ref="stdout"/>
+ </root>
+</configuration>
--- /dev/null
+package org.craftedsw.tripservicekata.trip;
+
+import org.craftedsw.tripservicekata.exception.CollaboratorCallException;
+import org.craftedsw.tripservicekata.user.User;
+import org.junit.Test;
+
+public class TripDAOShould {
+
+ @Test(expected = CollaboratorCallException.class) public void
+ throw_exception_when_retrieving_user_trips() {
+ new TripDAO().tripsBy(new User());
+ }
+}
--- /dev/null
+package org.craftedsw.tripservicekata.trip;\r
+\r
+import static org.hamcrest.core.Is.is;\r
+import static org.junit.Assert.assertThat;\r
+import static org.mockito.BDDMockito.given;\r
+\r
+import java.util.List;\r
+\r
+import org.craftedsw.tripservicekata.UserBuilder;\r
+import org.craftedsw.tripservicekata.exception.UserNotLoggedInException;\r
+import org.craftedsw.tripservicekata.user.User;\r
+import org.junit.Before;\r
+import org.junit.Test;\r
+import org.junit.runner.RunWith;\r
+import org.mockito.InjectMocks;\r
+import org.mockito.Mock;\r
+import org.mockito.Spy;\r
+import org.mockito.runners.MockitoJUnitRunner;\r
+\r
+@RunWith(MockitoJUnitRunner.class)\r
+public class TripServiceShould {\r
+ \r
+ private static final User GUEST = null;\r
+ private static final User UNUSED_USER = null;\r
+ private static final User REGISTERED_USER = new User();\r
+ private static final User ANOTHER_USER = new User();\r
+ private static final Trip TO_BRAZIL = new Trip();\r
+ private static final Trip TO_BERLIN = new Trip();\r
+ \r
+ @Mock TripDAO tripDAO;\r
+ @InjectMocks @Spy private TripService tripService;\r
+ \r
+ @Before\r
+ public void setUp() {\r
+ tripService = new TripService(tripDAO);\r
+ }\r
+\r
+ @Test(expected=UserNotLoggedInException.class) public void\r
+ throw_an_exception_when_user_is_not_logged_in() { \r
+ tripService.getTripsByUser(UNUSED_USER, GUEST);\r
+ }\r
+ \r
+ @Test public void\r
+ not_return_any_trips_when_users_are_not_friends() { \r
+ User friend = UserBuilder.aUser()\r
+ .friendsWith(ANOTHER_USER)\r
+ .withTrips(TO_BRAZIL)\r
+ .build();\r
+ \r
+ List<Trip> friendTrips = tripService.getTripsByUser(friend, REGISTERED_USER); \r
+ // You must always begin writing the assert.\r
+ // Remember: the assert must match the unit test method's name!!\r
+ // In this case, no trips must be returned.\r
+ assertThat(friendTrips.size(), is(0));\r
+ }\r
+ \r
+ @Test public void\r
+ return_friend_trips_when_users_are_friends() {\r
+ User friend = UserBuilder.aUser()\r
+ .friendsWith(ANOTHER_USER, REGISTERED_USER)\r
+ .withTrips(TO_BRAZIL, TO_BERLIN)\r
+ .build();\r
+ given(tripDAO.tripsBy(friend)).willReturn(friend.trips());\r
+ \r
+ List<Trip> friendTrips = tripService.getTripsByUser(friend, REGISTERED_USER); \r
+ // You must always begin writing the assert.\r
+ // Remember: the assert must match the unit test method's name!!\r
+ // In this case, no trips must be returned.\r
+ assertThat(friendTrips.size(), is(2));\r
+ }\r
+}\r
--- /dev/null
+package org.craftedsw.tripservicekata.user;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+
+import org.craftedsw.tripservicekata.UserBuilder;
+import org.junit.Test;
+
+public class UserShould {
+
+ private static final User JOHN = new User();
+ private static final User PAUL = new User();
+
+ @Test public void
+ inform_when_users_are_not_friends() {
+ User user = UserBuilder.aUser()
+ .friendsWith(JOHN)
+ .build();
+
+ assertThat(user.isFriendsWith(PAUL), is(false));
+ }
+
+ @Test public void
+ inform_when_users_are_friends() {
+ User user = UserBuilder.aUser()
+ .friendsWith(JOHN, PAUL)
+ .build();
+
+ assertThat(user.isFriendsWith(PAUL), is(true));
+ }
+}
+++ /dev/null
-package de.example.model.mapper;
-
-import de.example.model.resource.UserResource;
-
-
-public interface UsersMapper {
-
- public UserResource findOne(long id);
-
- public void create(UserResource user);
-}
+++ /dev/null
-package de.example.model.resource;
-
-
-public class UserResource {
- private final Long id;
- private final String name;
-
- public UserResource(Long id, String name) {
- this.id = id;
- this.name = name;
- }
-
- public Long getId() {
- return id;
- }
-
-
- public String getName() {
- return name;
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + ((name == null) ? 0 : name.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null) {
- return false;
- }
- if (getClass() != obj.getClass()) {
- return false;
- }
- UserResource other = (UserResource)obj;
- if (name == null) {
- if (other.name != null) {
- return false;
- }
- } else if (!name.equals(other.name)) {
- return false;
- }
- return true;
- }
-
-}
+++ /dev/null
-package de.example.services;
-
-import de.example.model.resource.UserResource;
-
-
-public interface UsersService {
-
- UserResource findOneById(long id);
-
- void create(UserResource user);
-
- String spiedMethod(UserResource user);
-}
+++ /dev/null
-package de.example.services.impl;
-
-import java.util.Objects;
-
-import de.example.model.mapper.UsersMapper;
-import de.example.model.resource.UserResource;
-import de.example.services.UsersService;
-
-
-public class UsersServiceImpl implements UsersService {
- private final UsersMapper usersMapper;
-
- public UsersServiceImpl(final UsersMapper usersMapper) {
- this.usersMapper = usersMapper;
- }
-
- @Override
- public UserResource findOneById(long id) {
- return this.usersMapper.findOne(id);
-
- }
-
- @Override
- public void create(final UserResource user) {
- Objects.requireNonNull(user, "user");
-
- this.spiedMethod(user);
-
- usersMapper.create(user);
- }
-
- @Override
- public String spiedMethod(final UserResource user) {
- return user.getName();
- }
-
-}
+++ /dev/null
-package de.example.tdd;
-
-import java.util.List;
-
-public class AddressBook {
-
- public List<Contact> getAllContacts() {
-
- return null;
- }
-
-}
+++ /dev/null
-package de.example.tdd;
-
-public class Calculator {
-
- public int sum(final int augend, final int addend) {
-
- return augend + addend;
- }
-
- public int product(final int multiplicand, final int multiplicier) {
-
- return multiplicand * multiplicier;
- }
-
-}
+++ /dev/null
-package de.example.tdd;
-
-public class Contact {
-
-}
+++ /dev/null
-package de.example.tdd;
-
-public class Dollar {
- int amount;
-
- public Dollar(final int amount) {
- this.amount = amount;
- }
-
- public void times(final int multiplier) {
- amount *= multiplier;
- }
-}
+++ /dev/null
-package de.example.tdd;
-
-public class Money {
-
-}
+++ /dev/null
-package org.craftedsw.tripservicekata;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.craftedsw.tripservicekata.exception.UserNotLoggedInException;
-import org.craftedsw.tripservicekata.trip.Trip;
-import org.craftedsw.tripservicekata.trip.TripDAO;
-import org.craftedsw.tripservicekata.user.User;
-import org.craftedsw.tripservicekata.user.UserSession;
-
-public class TripService_Original {
-
- public List<Trip> getTripsByUser(User user) throws UserNotLoggedInException {
- List<Trip> tripList = new ArrayList<Trip>();
- User loggedUser = UserSession.getInstance().getLoggedUser();
- boolean isFriend = false;
- if (loggedUser != null) {
- for (User friend : user.getFriends()) {
- if (friend.equals(loggedUser)) {
- isFriend = true;
- break;
- }
- }
- if (isFriend) {
- tripList = TripDAO.findTripsByUser(user);
- }
- return tripList;
- } else {
- throw new UserNotLoggedInException();
- }
- }
-
-}
+++ /dev/null
-package org.craftedsw.tripservicekata;
-
-import org.craftedsw.tripservicekata.trip.Trip;
-import org.craftedsw.tripservicekata.user.User;
-
-public class UserBuilder {
- private User[] friends = new User[]{};
- private Trip[] trips = new Trip[]{};
-
- public static UserBuilder aUser() {
- return new UserBuilder();
- }
-
- public UserBuilder withTrips(Trip...trips) {
- this.trips = trips;
- return this;
- }
-
- public UserBuilder friendsWith(User...friends) {
- this.friends = friends;
- return this;
- }
-
- public User build() {
- User user = new User();
- addTripsTo(user);
- addFriendsTo(user);
- return user;
- }
-
- private void addFriendsTo(User user) {
- for (User friend : friends) {
- user.addFriend(friend);
- }
- }
-
- private void addTripsTo(User user) {
- for (Trip trip : trips) {
- user.addTrip(trip);
- }
- }
-}
+++ /dev/null
-package org.craftedsw.tripservicekata.exception;
-
-public class CollaboratorCallException extends RuntimeException {
-
- private static final long serialVersionUID = -4584041339906109902L;
-
- public CollaboratorCallException() {
- super();
- }
-
- public CollaboratorCallException(String message,
- Throwable cause) {
- super(message, cause);
- }
-
- public CollaboratorCallException(String message) {
- super(message);
- }
-
- public CollaboratorCallException(Throwable cause) {
- super(cause);
- }
-
-
-}
+++ /dev/null
-package org.craftedsw.tripservicekata.exception;
-
-public class UserNotLoggedInException extends RuntimeException {
-
- private static final long serialVersionUID = 8959479918185637340L;
-
-}
+++ /dev/null
-package org.craftedsw.tripservicekata.trip;
-
-public class Trip {
-
-}
+++ /dev/null
-package org.craftedsw.tripservicekata.trip;
-
-import java.util.List;
-
-import org.craftedsw.tripservicekata.exception.CollaboratorCallException;
-import org.craftedsw.tripservicekata.user.User;
-
-public class TripDAO {
-
- public static List<Trip> findTripsByUser(User user) {
- throw new CollaboratorCallException(
- "TripDAO should not be invoked on an unit test.");
- }
-
- // At the end and by means of many code refactoring our app
- // will end up using the instance method. In the meanwhile we
- // will not be able to remove the static method but at least
- // we are offering something that can be tested.
- // Be careful when refactoring this code because in real life
- // findTripsByUser for sure will be used in many places but again
- // in the meanwhile with this code we can write unit tests (we
- // always need to write a test of our legacy code before refactoring it)
- public List<Trip> tripsBy(User user) {
- return TripDAO.findTripsByUser(user);
- }
-
-}
\ No newline at end of file
+++ /dev/null
-package org.craftedsw.tripservicekata.trip;\r
-\r
-import java.util.ArrayList;\r
-import java.util.List;\r
-\r
-import javax.inject.Inject;\r
-\r
-import org.craftedsw.tripservicekata.exception.UserNotLoggedInException;\r
-import org.craftedsw.tripservicekata.user.User;\r
-\r
-public class TripService {\r
- \r
- private final TripDAO tripDAO;\r
-\r
- @Inject\r
- public TripService(TripDAO tripDAO) {\r
- this.tripDAO = tripDAO;\r
- }\r
-\r
- public List<Trip> getTripsByUser(User user, User loggedInUser) throws UserNotLoggedInException {\r
- validate(loggedInUser);\r
- \r
- return user.isFriendsWith(loggedInUser)\r
- ? tripsBy(user)\r
- : noTrips();\r
- }\r
-\r
- private void validate(User loggedInUser) {\r
- if (loggedInUser == null) {\r
- throw new UserNotLoggedInException();\r
- }\r
- }\r
-\r
- private ArrayList<Trip> noTrips() {\r
- return new ArrayList<>();\r
- }\r
-\r
- private List<Trip> tripsBy(User user) {\r
- return tripDAO.tripsBy(user);\r
- }\r
-}\r
+++ /dev/null
-package org.craftedsw.tripservicekata.user;\r
-\r
-import java.util.ArrayList;\r
-import java.util.List;\r
-\r
-import org.craftedsw.tripservicekata.trip.Trip;\r
-\r
-public class User {\r
-\r
- private List<Trip> trips = new ArrayList<Trip>();\r
- private List<User> friends = new ArrayList<User>();\r
- \r
- public List<User> getFriends() {\r
- return friends;\r
- }\r
- \r
- public void addFriend(User user) {\r
- friends.add(user);\r
- }\r
-\r
- public void addTrip(Trip trip) {\r
- trips.add(trip);\r
- }\r
- \r
- public List<Trip> trips() {\r
- return trips;\r
- }\r
-\r
- public boolean isFriendsWith(User anotherUser) {\r
- return friends.contains(anotherUser);\r
- }\r
-\r
-}\r
+++ /dev/null
-package org.craftedsw.tripservicekata.user;
-
-import org.craftedsw.tripservicekata.exception.CollaboratorCallException;
-
-public class UserSession {
-
- private static final UserSession userSession = new UserSession();
-
- private UserSession() {
- }
-
- public static UserSession getInstance() {
- return userSession;
- }
-
- public User getLoggedUser() {
- throw new CollaboratorCallException(
- "UserSession.getLoggedUser() should not be called in an unit test");
- }
-
-}
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
- * name: The name of the configuration.
- * dest: Either "err", which will send output to stderr, or a file path or URL.
- * status: The level of internal Log4j events that should be logged to the console. Valid values for this attribute are "trace",
- "debug", "info", "warn", "error" and "fatal". Log4j will log details about initialization, rollover and other internal
- actions to the status logger.
- * strict: Enables the use of the strict XML format. Not supported in JSON configurations.
- * monitorInterval: If the monitorInterval attribute is specified on the configuration element and is set to a non-zero value
- then the file will be checked the next time a log event is evaluated and/or logged and the monitorInterval has elapsed
- since the last check.
- * packages: A comma separated list of package names to search for plugins. Plugins are only loaded once per classloader so
- changing this value may not have any effect upon reconfiguration.
--->
-<Configuration name="XMLConfigTest" dest="err" status="trace" strict="true" monitorInterval="30" packages="org.apache.logging.log4j.test">
- <!--
- Log4j 2 supports the ability to specify tokens in the configuration as references to properties defined elsewhere. Some of
- these properties will be resolved when the configuration file is interpreted while others may be passed to components
- where they will be evaluated at runtime.
- -->
- <Properties>
- <Property name="filename">target/fileappender.log</Property>
- <Property name="filerandomname">target/filerandomappender.log</Property>
- </Properties>
-
- <!--
- Filters may be configured in one of four locations:
-
- Context-wide Filters are configured directly in the configuration. Events that are rejected by these filters
- will not be passed to loggers for further processing. Once an event has been accepted by a Context-wide filter it will not be evaluated by any other Context-wide Filters nor will the Logger's Level be used to filter the event. The event will be evaluated by Logger and Appender Filters however.
- Logger Filters are configured on a specified Logger. These are evaluated after the Context-wide Filters and the Log Level for the Logger. Events that are rejected by these filters will be discarded and the event will not be passed to a parent Logger regardless of the additivity setting.
- Appender Filters are used to determine if a specific Appender should handle the formatting and publication of the event.
- Appender Reference Filters are used to determine if a Logger should route the event to an appender.
-
- <Filter type="ThresholdFilter" level="debug"/>
- -->
-
- <Appenders>
- <Appender type="Console" name="STDOUT">
- <!--
- <ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/>
- -->
- <Filter type="ThresholdFilter" level="trace"/>
- <!--
- <Layout type="PatternLayout" pattern="%d{ABSOLUTE} %5p %c{1}:%L %m%n"/>
- -->
- <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx - %X{id} %X{name} %X{surname}%n"/>
- <!--
- <Filters>
- See: FlowTracingExample class in this project.
- <Filter type="MarkerFilter" marker="FLOW" onMatch="ENTER" onMismatch="NEUTRAL"/>
- <Filter type="MarkerFilter" marker="EXCEPTION" onMatch="CATCHING" onMismatch="ACCEPT"/>
-
- See: MarkersExample class in this project.
- ¿Cómo usar esto cuando hago logger.debug(QUERY_MARKER, "SELECT * FROM {}", table);?
- ¿Sería marker="DEBUG"?
- <Filter type="MarkerFilter" marker="FLOW" onMatch="SQL_UPDATE" onMismatch="NEUTRAL"/>
- <Filter type="MarkerFilter" marker="EXCEPTION" onMatch="SQL_QUERY" onMismatch="ACCEPT"/>
- </Filters>
- -->
- </Appender>
- <Appender type="Console" name="FLOW">
- <Layout type="PatternLayout" pattern="%C{1}.%M %m %ex%n"/><!-- class and line number -->
- <Filters>
- <Filter type="MarkerFilter" marker="FLOW" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
- <Filter type="MarkerFilter" marker="EXCEPTION" onMatch="ACCEPT" onMismatch="DENY"/>
- </Filters>
- </Appender>
- <!--
- ¿Puedo mezclar bufferedIO=true con immediateFlush=true?
- LOG4J 1.2: bufferedIO="false" immediateFlush="true" (es como se ejecuta actualmente la N2A)
- JUGAR CON ESOS 2 VALORES A VER QUE TAL... :/
- -->
- <Appender type="File" name="FileAppender" fileName="${filename}" bufferedIO="false" immediateFlush="true" append="true">
- <Layout type="PatternLayout">
- <!--
- <Pattern>%d %p %C{1.} [%t] %m%n</Pattern>
- -->
- <Pattern>"%d{yyyyMMddHHmmssSSS} - %-5p - [%t] - %m%n"</Pattern>
- <Charset>UTF-8</Charset>
- </Layout>
- </Appender>
- <!--
- * Asynchronous Loggers are a new addition to Log4j 2. Their aim is to return from the call to Logger.log to the application
- as soon as possible. You can choose between making all Loggers asynchronous or using a mixture of synchronous and asynchronous
- Loggers. Making all Loggers asynchronous will give the best performance, while mixing gives you more flexibility.
- * LMAX Disruptor technology. Asynchronous Loggers internally use the Disruptor, a lock-free inter-thread communication library,
- instead of queues, resulting in higher throughput and lower latency.
- * Asynchronous Appenders already existed in Log4j 1.x, but have been enhanced to flush to disk at the end of a batch
- (when the queue is empty). This produces the same result as configuring "immediateFlush=true", that is, all received log events
- are always available on disk, but is more efficient because it does not need to touch the disk on each and every log event.
- (Async Appenders use ArrayBlockingQueue internally and do not need the disruptor jar on the classpath.)
- * (For synchronous and asynchronous use) Random Access File Appenders are an alternative to Buffered File Appenders. Under the hood,
- these new appenders use a ByteBuffer + RandomAccessFile instead of a BufferedOutputStream. In our testing this was about 20-200%
- faster. These appenders can also be used with synchronous loggers and will give the same performance benefits. Random Access
- File Appenders do not need the disruptor jar on the classpath.
- -->
- <!-- El random access file siempre es con buffer!!!
- When set to true - the default, each write will be followed by a flush. This will guarantee the data is written
- to disk but could impact performance.
-
- Flushing after every write is only useful when using this appender with synchronous loggers. Asynchronous loggers
- and appenders will automatically flush at the end of a batch of events, even if immediateFlush is set to false.
- This also guarantees the data is written to disk but is more efficient.
- -->
- <Appender type="RandomAccessFile" name="RandomAccessFileAppender" fileName="${filerandomname}" immediateFlush="true" append="true">
- <Layout type="PatternLayout">
- <Pattern>"%d{yyyyMMddHHmmssSSS} - %-5p - [%t] - %m%n"</Pattern>
- <Charset>UTF-8</Charset>
- </Layout>
- </Appender>
- <!--
- El AsyncAppender siempre es bufferado y hace siempre un immediateFlush ¿independientemente del appender ref? SÍ, mira:
-
- Mejor usar includeLocation=false proque el AsyncAppender siempre debe hacer la localización del log (nombre de clase, método y línea)
- independientemente de si luego se va a loguear o no (porque haya filtros por ejemplo) Esto es así porque el asyncappender
- debe generar toda la info necesaria antes de pasar esta info a la cola. Una vez en la cola asíncrona esa info ya no puede
- ser generada. Por tanto, aunque ese log finalmente no se use, siempre se pone en la cola asíncrona con todos sus datos.
-
- Extracting location is an expensive operation (it can make logging 5 - 20 times slower). To improve performance, location
- is not included by default when adding a log event to the queue. You can change this by setting includeLocation="true".
- Para extraer la localización al parecer hay que recorrer el stacktrace por eso es una operacion pesada.
- -->
- <Appender type="Async" name="AsyncAppender" blocking="true" bufferSize="128" errorRef="FileAppender" ignoreExceptions="false" includeLocation="false">
- <!--
- Probar las dos configuraciones
- <AppenderRef ref="RandomAccessFileAppender"/>
- -->
- <AppenderRef ref="FileAppender"/>
- </Appender>
-
- </Appenders>
-
- <Loggers>
- <!--
- <Logger name="de.loggers.test" level="debug" additivity="false">
- <Filter type="ThreadContextMapFilter">
- <KeyValuePair key="test" value="123"/>
- </Filter>
- <AppenderRef ref="STDOUT"/>
- </Logger>
- -->
-
- <!--
- <Logger name="de.loggers.test" level="debug" additivity="false">
- <AppenderRef ref="File"/>
- </Logger>
- -->
-
- <Logger name="de.loggers.test.simple" level="TRACE">
- <!--
- * Because THIS logger has additivity="true" by default THIS logger will use the same appender as its parent's logger,
- which in this case is the logger Root.
- * Because THIS logger has additivity="true" by default if we use this AppenderRef we will see for
- classes in de.loggers.test.simple the same messages twice. One from this logger and one from its parent's logger,
- which in this case is the logger Root.
- <AppenderRef ref="STDOUT"/>
- -->
- </Logger>
- <Logger name="de.loggers.test.formatter" level="TRACE" additivity="false">
- <!--
- * Because THIS logger has additivity="false" THIS logger will not use the same appender as its parent's logger.
- -->
- <AppenderRef ref="STDOUT"/>
- </Logger>
- <!--
- By default, location is not passed to the I/O thread by asynchronous loggers. If one of your layouts or custom
- filters needs location information, you need to set "includeLocation=true" in the configuration of all relevant loggers,
- including the root logger.
- -->
-
-
-
- <!--
- * Asynchronous Loggers are a new addition to Log4j 2. Their aim is to return from the call to Logger.log to the application
- as soon as possible. You can choose between making all Loggers asynchronous or using a mixture of synchronous and asynchronous
- Loggers. Making all Loggers asynchronous will give the best performance, while mixing gives you more flexibility.
- * LMAX Disruptor technology. Asynchronous Loggers internally use the Disruptor, a lock-free inter-thread communication library,
- instead of queues, resulting in higher throughput and lower latency.
- * Asynchronous Appenders already existed in Log4j 1.x, but have been enhanced to flush to disk at the end of a batch
- (when the queue is empty). This produces the same result as configuring "immediateFlush=true", that is, all received log events
- are always available on disk, but is more efficient because it does not need to touch the disk on each and every log event.
- (Async Appenders use ArrayBlockingQueue internally and do not need the disruptor jar on the classpath.)
- * (For synchronous and asynchronous use) Random Access File Appenders are an alternative to Buffered File Appenders. Under the hood,
- these new appenders use a ByteBuffer + RandomAccessFile instead of a BufferedOutputStream. In our testing this was about 20-200%
- faster. These appenders can also be used with synchronous loggers and will give the same performance benefits. Random Access
- File Appenders do not need the disruptor jar on the classpath.
- -->
- <AsyncLogger name="de.loggers.test.loggernames" level="TRACE" includeLocation="false">
- <AppenderRef ref="RandomAccessFileAppender"/>
- </AsyncLogger>
- <Root level="TRACE">
- <AppenderRef ref="STDOUT"/>
- </Root>
- </Loggers>
-
-</Configuration>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-
-<!-- For assistance related to logback-translator or configuration -->
-<!-- files in general, please contact the logback user mailing list -->
-<!-- at http://www.qos.ch/mailman/listinfo/logback-user -->
-<!-- -->
-<!-- For professional support please see -->
-<!-- http://www.qos.ch/shop/products/professionalSupport -->
-<!-- -->
-<configuration>
- <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
- <encoder>
- <pattern>%d{yyyyMMddHHmmssSSS} - %-5p - [%t] - [%C] - %m%n</pattern>
- </encoder>
- </appender>
- <root level="INFO">
- <appender-ref ref="stdout"/>
- </root>
-</configuration>
+++ /dev/null
-package de.example.services;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-
-import de.example.model.mapper.UsersMapper;
-import de.example.model.resource.UserResource;
-import de.example.services.impl.UsersServiceImpl;
-
-
-public class UsersServiceTest {
- private static final long ID = 10L;
-
- private UsersService usersService;
- private UsersMapper usersMapper;
-
- @Before
- public void setUp() throws Exception {
- usersMapper = mock(UsersMapper.class);
- // USING spy because we want to spy UsersService.spiedMethod
- // otherwise we wouldn't need org.mockito.Mockito.spy.
- usersService = spy(new UsersServiceImpl(usersMapper));
- }
-
- @Test
- public void givenSomeIdWhenFindUserThenReturnUser() {
- // Arrange
- UserResource expectedUser = new UserResource(ID, "Gustavo");
- when(usersMapper.findOne(ID)).thenReturn(expectedUser);
-
- // Act
- UserResource user = usersService.findOneById(ID);
-
- // Assert
- assertEquals(expectedUser, user);
- }
-
-
- @Test
- public void givenNewUserDataWhenCreateNewUserThenCreateUser() {
- // Arrange
- UserResource expectedUser = new UserResource(ID, "Gustavo");
- UserResource newUser = new UserResource(null, "Gustavo");
-
- // Act
- usersService.create(newUser);
-
- // Assert
- verify(usersService).spiedMethod(newUser);
- verify(usersMapper).create(newUser);
- assertEquals(expectedUser, newUser);
- }
-
- @Test(expected=NullPointerException.class)
- public void givenNoUserDataWhenCreateNewUserThenThrowNullPointerException() {
- // Act
- usersService.create(null);
- }
-
- @Test
- public void givenNewUserDataWhenCreateNewUserThenCreateUserWithArgumentCaptor() {
- // Arrange
- UserResource newUser = new UserResource(null, "Gustavo");
- ArgumentCaptor<UserResource> argument = ArgumentCaptor.forClass(UserResource.class);
-
- // Act
- usersService.create(newUser);
-
- // Assert
- verify(usersService).spiedMethod(newUser);
- verify(usersMapper).create(argument.capture());
- assertEquals(argument.getValue(), newUser);
- }
-}
+++ /dev/null
-package de.example.tdd;
-
-import static org.junit.Assert.assertEquals;
-
-import java.util.List;
-
-import org.junit.Test;
-
-public class AddressBookTest {
-
- @Test
- public void testGivenAndExistingAddressBookWhenRetrievingTheContactsThenListWithContacts() {
- //AddressBook addressBook = new AddressBook();
-
-
- //List<Contact> contacts = addressBook.getAllContacts();
- //int size = contacts.size();
-
- // Assert
- assertEquals(16, 16);
- }
-}
+++ /dev/null
-package de.example.tdd;
-
-import static org.junit.Assert.*;
-
-import org.junit.Before;
-import org.junit.Test;
-
-public class CalculatorTest {
- private Calculator calculator;
-
- @Before
- public void setUp() throws Exception {
- // Arrange
- calculator = new Calculator();
- }
-
- @Test
- public void testGivenTwoNumbersWhenAddingThenResult() {
- // Arrange
-
-
- // Act
- int result = calculator.sum(2, 2);
-
- // Assert
- assertEquals(4, result);
-
-
- // Act
- result = calculator.sum(8, 8);
-
- // Assert
- assertEquals(16, result);
- }
-
- @Test
- public void testGivenTwoNumbersWhenMultiplicationThenProduct() {
-
-
- assertEquals(16, calculator.product(4, 4));
-
- assertEquals(64, calculator.product(8, 8));
- }
-}
+++ /dev/null
-package de.example.tdd;
-
-import static org.junit.Assert.*;
-
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-public class MoneyTest {
-
- /**
- * @throws java.lang.Exception
- */
- @BeforeClass
- public static void setUpBeforeClass() throws Exception {
- }
-
- /**
- * @throws java.lang.Exception
- */
- @AfterClass
- public static void tearDownAfterClass() throws Exception {
- }
-
- /**
- * @throws java.lang.Exception
- */
- @Before
- public void setUp() throws Exception {
- }
-
- /**
- * @throws java.lang.Exception
- */
- @After
- public void tearDown() throws Exception {
- }
-
- @Test
- public void testMultiplication()
- {
- Dollar five = new Dollar(5);
- five.times(2);
-
- assertEquals(10, five.amount);
- }
-}
+++ /dev/null
-package org.craftedsw.tripservicekata.trip;
-
-import org.craftedsw.tripservicekata.exception.CollaboratorCallException;
-import org.craftedsw.tripservicekata.user.User;
-import org.junit.Test;
-
-public class TripDAOShould {
-
- @Test(expected = CollaboratorCallException.class) public void
- throw_exception_when_retrieving_user_trips() {
- new TripDAO().tripsBy(new User());
- }
-}
+++ /dev/null
-package org.craftedsw.tripservicekata.trip;\r
-\r
-import static org.hamcrest.core.Is.is;\r
-import static org.junit.Assert.assertThat;\r
-import static org.mockito.BDDMockito.given;\r
-\r
-import java.util.List;\r
-\r
-import org.craftedsw.tripservicekata.UserBuilder;\r
-import org.craftedsw.tripservicekata.exception.UserNotLoggedInException;\r
-import org.craftedsw.tripservicekata.user.User;\r
-import org.junit.Before;\r
-import org.junit.Test;\r
-import org.junit.runner.RunWith;\r
-import org.mockito.InjectMocks;\r
-import org.mockito.Mock;\r
-import org.mockito.Spy;\r
-import org.mockito.runners.MockitoJUnitRunner;\r
-\r
-@RunWith(MockitoJUnitRunner.class)\r
-public class TripServiceShould {\r
- \r
- private static final User GUEST = null;\r
- private static final User UNUSED_USER = null;\r
- private static final User REGISTERED_USER = new User();\r
- private static final User ANOTHER_USER = new User();\r
- private static final Trip TO_BRAZIL = new Trip();\r
- private static final Trip TO_BERLIN = new Trip();\r
- \r
- @Mock TripDAO tripDAO;\r
- @InjectMocks @Spy private TripService tripService;\r
- \r
- @Before\r
- public void setUp() {\r
- tripService = new TripService(tripDAO);\r
- }\r
-\r
- @Test(expected=UserNotLoggedInException.class) public void\r
- throw_an_exception_when_user_is_not_logged_in() { \r
- tripService.getTripsByUser(UNUSED_USER, GUEST);\r
- }\r
- \r
- @Test public void\r
- not_return_any_trips_when_users_are_not_friends() { \r
- User friend = UserBuilder.aUser()\r
- .friendsWith(ANOTHER_USER)\r
- .withTrips(TO_BRAZIL)\r
- .build();\r
- \r
- List<Trip> friendTrips = tripService.getTripsByUser(friend, REGISTERED_USER); \r
- // You must always begin writing the assert.\r
- // Remember: the assert must match the unit test method's name!!\r
- // In this case, no trips must be returned.\r
- assertThat(friendTrips.size(), is(0));\r
- }\r
- \r
- @Test public void\r
- return_friend_trips_when_users_are_friends() {\r
- User friend = UserBuilder.aUser()\r
- .friendsWith(ANOTHER_USER, REGISTERED_USER)\r
- .withTrips(TO_BRAZIL, TO_BERLIN)\r
- .build();\r
- given(tripDAO.tripsBy(friend)).willReturn(friend.trips());\r
- \r
- List<Trip> friendTrips = tripService.getTripsByUser(friend, REGISTERED_USER); \r
- // You must always begin writing the assert.\r
- // Remember: the assert must match the unit test method's name!!\r
- // In this case, no trips must be returned.\r
- assertThat(friendTrips.size(), is(2));\r
- }\r
-}\r
+++ /dev/null
-package org.craftedsw.tripservicekata.user;
-
-import static org.hamcrest.core.Is.is;
-import static org.junit.Assert.assertThat;
-
-import org.craftedsw.tripservicekata.UserBuilder;
-import org.junit.Test;
-
-public class UserShould {
-
- private static final User JOHN = new User();
- private static final User PAUL = new User();
-
- @Test public void
- inform_when_users_are_not_friends() {
- User user = UserBuilder.aUser()
- .friendsWith(JOHN)
- .build();
-
- assertThat(user.isFriendsWith(PAUL), is(false));
- }
-
- @Test public void
- inform_when_users_are_friends() {
- User user = UserBuilder.aUser()
- .friendsWith(JOHN, PAUL)
- .build();
-
- assertThat(user.isFriendsWith(PAUL), is(true));
- }
-}
--- /dev/null
+Sandro Mancuso: testing a refactoring legacy code
+
+Rules for refactoring legacy code.
+
+1. You may not change production code if not covered by tests. Before refactoring some legacy code we must write a Unit Test for it.
+
+ But some times for writing the test we must make modifications in the legacy code. If such modifications are required: just automated refactoring (via IDES) are allowed, if needed to write the test.
+
+
+Steps:
+
+1. Start testing the legacy code following the shortest branch.
+
+2. In Unit Test we should not invoke other classes but the one being under test (the other classes should be stub, mocke, etc, etc)
+
+3. When the legacy code is covered by a unit test we may refactor it (never before) We have to refactor our legacy code from the deepest to the shortest branch.
+
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<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/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>de.example.tdd</groupId>
+ <artifactId>tdd-by-example</artifactId>
+ <packaging>jar</packaging>
+ <version>1.0-SNAPSHOT</version>
+ <name>tdd-by-example</name>
+ <url>http://gumartinm.name</url>
+ <description>TDD by example</description>
+ <organization>
+ <name>Gustavo Martin Morcuende</name>
+ <url>http://www.gumartinm.name</url>
+ </organization>
+ <scm>
+ <developerConnection>scm:git:http://git.gumartinm.name/TDDbyExample</developerConnection>
+ <url>http://git.gumartinm.name/TDDbyExample</url>
+ </scm>
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ </properties>
+ <dependencies>
+ <!--
+ **************************************************************************************************************************
+ 1. LOGBACK: SLF4J NATIVE IMPLEMENTATION. See: http://logback.qos.ch/
+ **************************************************************************************************************************
+ -->
+ <!--
+ 1/1 Required dependency for native implementation (nothing else is required)
+
+ Native implementation. There are also SLF4J bindings external to the SLF4J project, e.g. logback which implements
+ SLF4Jnatively. Logback's ch.qos.logback.classic.Logger class is a direct implementation of SLF4J's org.slf4j.Logger
+ interface. Thus, using SLF4J in conjunction with logback involves strictly zero memory and computational overhead.
+ see: http://www.slf4j.org/manual.html
+ -->
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-classic</artifactId>
+ <version>1.1.2</version>
+ </dependency>
+ <!--
+ **************************************************************************************************************************
+ 2. SLF4J AND LOG4J 2. See: http://logging.apache.org/log4j/2.x/
+ **************************************************************************************************************************
+ -->
+
+ <!--
+ 1/2 Required dependency for log4j 2 with slf4j: binding
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-slf4j-impl</artifactId>
+ <version>2.0-rc1</version>
+ </dependency>
+ -->
+ <!--
+ 2/2 Required dependency for log4j 2 with slf4j: log4j 2 maven plugin
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-core</artifactId>
+ <version>2.0-rc1</version>
+ </dependency>
+ -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.12-beta-1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>2.0.31-beta</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>3.1</version>
+ <configuration>
+ <source>1.8</source>
+ <target>1.8</target>
+ <!--
+ <source>1.6</source>
+ <target>1.6</target>
+ -->
+ <encoding>${project.build.sourceEncoding}</encoding>
+ <compilerArgument>-Xlint:deprecation</compilerArgument>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-resources-plugin</artifactId>
+ <version>2.6</version>
+ <configuration>
+ <encoding>${project.build.sourceEncoding}</encoding>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <version>2.17</version>
+ <!--
+ Trying autodetection, see: http://maven.apache.org/surefire/maven-surefire-plugin/examples/junit.html
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.maven.surefire</groupId>
+ <artifactId>surefire-junit47</artifactId>
+ <version>2.17</version>
+ </dependency>
+ </dependencies>
+ -->
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <version>2.3.1</version>
+ <configuration>
+ <archive>
+ <manifestEntries>
+ <Specification-Title>${project.description}</Specification-Title>
+ <Specification-Version>${project.version}</Specification-Version>
+ <Specification-Vendor>${project.organization.name}</Specification-Vendor>
+ <Implementation-Title>${project.description}</Implementation-Title>
+ <Implementation-Version>${project.version}</Implementation-Version>
+ <Implementation-Vendor>${project.organization.name}</Implementation-Vendor>
+ </manifestEntries>
+ </archive>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <reporting>
+ <plugins>
+ <plugin>
+ <!--
+ <groupId>org.jacoco</groupId>
+ <artifactId>jacoco</artifactId>
+ <version>0.7.2.201409121644</version>
+ -->
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>cobertura-maven-plugin</artifactId>
+ <version>2.6</version>
+ </plugin>
+ </plugins>
+ </reporting>
+</project>
--- /dev/null
+package de.example.model.mapper;
+
+import de.example.model.resource.UserResource;
+
+
+public interface UsersMapper {
+
+ public UserResource findOne(long id);
+
+ public void create(UserResource user);
+}
--- /dev/null
+package de.example.model.resource;
+
+
+public class UserResource {
+ private final Long id;
+ private final String name;
+
+ public UserResource(Long id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((name == null) ? 0 : name.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ UserResource other = (UserResource)obj;
+ if (name == null) {
+ if (other.name != null) {
+ return false;
+ }
+ } else if (!name.equals(other.name)) {
+ return false;
+ }
+ return true;
+ }
+
+}
--- /dev/null
+package de.example.services;
+
+import de.example.model.resource.UserResource;
+
+
+public interface UsersService {
+
+ UserResource findOneById(long id);
+
+ void create(UserResource user);
+
+ String spiedMethod(UserResource user);
+}
--- /dev/null
+package de.example.services.impl;
+
+import java.util.Objects;
+
+import de.example.model.mapper.UsersMapper;
+import de.example.model.resource.UserResource;
+import de.example.services.UsersService;
+
+
+public class UsersServiceImpl implements UsersService {
+ private final UsersMapper usersMapper;
+
+ public UsersServiceImpl(final UsersMapper usersMapper) {
+ this.usersMapper = usersMapper;
+ }
+
+ @Override
+ public UserResource findOneById(long id) {
+ return this.usersMapper.findOne(id);
+
+ }
+
+ @Override
+ public void create(final UserResource user) {
+ Objects.requireNonNull(user, "user");
+
+ this.spiedMethod(user);
+
+ usersMapper.create(user);
+ }
+
+ @Override
+ public String spiedMethod(final UserResource user) {
+ return user.getName();
+ }
+
+}
--- /dev/null
+package de.example.tdd;
+
+import java.util.List;
+
+public class AddressBook {
+
+ public List<Contact> getAllContacts() {
+
+ return null;
+ }
+
+}
--- /dev/null
+package de.example.tdd;
+
+public class Calculator {
+
+ public int sum(final int augend, final int addend) {
+
+ return augend + addend;
+ }
+
+ public int product(final int multiplicand, final int multiplicier) {
+
+ return multiplicand * multiplicier;
+ }
+
+}
--- /dev/null
+package de.example.tdd;
+
+public class Contact {
+
+}
--- /dev/null
+package de.example.tdd;
+
+public class Dollar {
+ int amount;
+
+ public Dollar(final int amount) {
+ this.amount = amount;
+ }
+
+ public void times(final int multiplier) {
+ amount *= multiplier;
+ }
+}
--- /dev/null
+package de.example.tdd;
+
+public class Money {
+
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * name: The name of the configuration.
+ * dest: Either "err", which will send output to stderr, or a file path or URL.
+ * status: The level of internal Log4j events that should be logged to the console. Valid values for this attribute are "trace",
+ "debug", "info", "warn", "error" and "fatal". Log4j will log details about initialization, rollover and other internal
+ actions to the status logger.
+ * strict: Enables the use of the strict XML format. Not supported in JSON configurations.
+ * monitorInterval: If the monitorInterval attribute is specified on the configuration element and is set to a non-zero value
+ then the file will be checked the next time a log event is evaluated and/or logged and the monitorInterval has elapsed
+ since the last check.
+ * packages: A comma separated list of package names to search for plugins. Plugins are only loaded once per classloader so
+ changing this value may not have any effect upon reconfiguration.
+-->
+<Configuration name="XMLConfigTest" dest="err" status="trace" strict="true" monitorInterval="30" packages="org.apache.logging.log4j.test">
+ <!--
+ Log4j 2 supports the ability to specify tokens in the configuration as references to properties defined elsewhere. Some of
+ these properties will be resolved when the configuration file is interpreted while others may be passed to components
+ where they will be evaluated at runtime.
+ -->
+ <Properties>
+ <Property name="filename">target/fileappender.log</Property>
+ <Property name="filerandomname">target/filerandomappender.log</Property>
+ </Properties>
+
+ <!--
+ Filters may be configured in one of four locations:
+
+ Context-wide Filters are configured directly in the configuration. Events that are rejected by these filters
+ will not be passed to loggers for further processing. Once an event has been accepted by a Context-wide filter it will not be evaluated by any other Context-wide Filters nor will the Logger's Level be used to filter the event. The event will be evaluated by Logger and Appender Filters however.
+ Logger Filters are configured on a specified Logger. These are evaluated after the Context-wide Filters and the Log Level for the Logger. Events that are rejected by these filters will be discarded and the event will not be passed to a parent Logger regardless of the additivity setting.
+ Appender Filters are used to determine if a specific Appender should handle the formatting and publication of the event.
+ Appender Reference Filters are used to determine if a Logger should route the event to an appender.
+
+ <Filter type="ThresholdFilter" level="debug"/>
+ -->
+
+ <Appenders>
+ <Appender type="Console" name="STDOUT">
+ <!--
+ <ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/>
+ -->
+ <Filter type="ThresholdFilter" level="trace"/>
+ <!--
+ <Layout type="PatternLayout" pattern="%d{ABSOLUTE} %5p %c{1}:%L %m%n"/>
+ -->
+ <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx - %X{id} %X{name} %X{surname}%n"/>
+ <!--
+ <Filters>
+ See: FlowTracingExample class in this project.
+ <Filter type="MarkerFilter" marker="FLOW" onMatch="ENTER" onMismatch="NEUTRAL"/>
+ <Filter type="MarkerFilter" marker="EXCEPTION" onMatch="CATCHING" onMismatch="ACCEPT"/>
+
+ See: MarkersExample class in this project.
+ ¿Cómo usar esto cuando hago logger.debug(QUERY_MARKER, "SELECT * FROM {}", table);?
+ ¿Sería marker="DEBUG"?
+ <Filter type="MarkerFilter" marker="FLOW" onMatch="SQL_UPDATE" onMismatch="NEUTRAL"/>
+ <Filter type="MarkerFilter" marker="EXCEPTION" onMatch="SQL_QUERY" onMismatch="ACCEPT"/>
+ </Filters>
+ -->
+ </Appender>
+ <Appender type="Console" name="FLOW">
+ <Layout type="PatternLayout" pattern="%C{1}.%M %m %ex%n"/><!-- class and line number -->
+ <Filters>
+ <Filter type="MarkerFilter" marker="FLOW" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
+ <Filter type="MarkerFilter" marker="EXCEPTION" onMatch="ACCEPT" onMismatch="DENY"/>
+ </Filters>
+ </Appender>
+ <!--
+ ¿Puedo mezclar bufferedIO=true con immediateFlush=true?
+ LOG4J 1.2: bufferedIO="false" immediateFlush="true" (es como se ejecuta actualmente la N2A)
+ JUGAR CON ESOS 2 VALORES A VER QUE TAL... :/
+ -->
+ <Appender type="File" name="FileAppender" fileName="${filename}" bufferedIO="false" immediateFlush="true" append="true">
+ <Layout type="PatternLayout">
+ <!--
+ <Pattern>%d %p %C{1.} [%t] %m%n</Pattern>
+ -->
+ <Pattern>"%d{yyyyMMddHHmmssSSS} - %-5p - [%t] - %m%n"</Pattern>
+ <Charset>UTF-8</Charset>
+ </Layout>
+ </Appender>
+ <!--
+ * Asynchronous Loggers are a new addition to Log4j 2. Their aim is to return from the call to Logger.log to the application
+ as soon as possible. You can choose between making all Loggers asynchronous or using a mixture of synchronous and asynchronous
+ Loggers. Making all Loggers asynchronous will give the best performance, while mixing gives you more flexibility.
+ * LMAX Disruptor technology. Asynchronous Loggers internally use the Disruptor, a lock-free inter-thread communication library,
+ instead of queues, resulting in higher throughput and lower latency.
+ * Asynchronous Appenders already existed in Log4j 1.x, but have been enhanced to flush to disk at the end of a batch
+ (when the queue is empty). This produces the same result as configuring "immediateFlush=true", that is, all received log events
+ are always available on disk, but is more efficient because it does not need to touch the disk on each and every log event.
+ (Async Appenders use ArrayBlockingQueue internally and do not need the disruptor jar on the classpath.)
+ * (For synchronous and asynchronous use) Random Access File Appenders are an alternative to Buffered File Appenders. Under the hood,
+ these new appenders use a ByteBuffer + RandomAccessFile instead of a BufferedOutputStream. In our testing this was about 20-200%
+ faster. These appenders can also be used with synchronous loggers and will give the same performance benefits. Random Access
+ File Appenders do not need the disruptor jar on the classpath.
+ -->
+ <!-- El random access file siempre es con buffer!!!
+ When set to true - the default, each write will be followed by a flush. This will guarantee the data is written
+ to disk but could impact performance.
+
+ Flushing after every write is only useful when using this appender with synchronous loggers. Asynchronous loggers
+ and appenders will automatically flush at the end of a batch of events, even if immediateFlush is set to false.
+ This also guarantees the data is written to disk but is more efficient.
+ -->
+ <Appender type="RandomAccessFile" name="RandomAccessFileAppender" fileName="${filerandomname}" immediateFlush="true" append="true">
+ <Layout type="PatternLayout">
+ <Pattern>"%d{yyyyMMddHHmmssSSS} - %-5p - [%t] - %m%n"</Pattern>
+ <Charset>UTF-8</Charset>
+ </Layout>
+ </Appender>
+ <!--
+ El AsyncAppender siempre es bufferado y hace siempre un immediateFlush ¿independientemente del appender ref? SÍ, mira:
+
+ Mejor usar includeLocation=false proque el AsyncAppender siempre debe hacer la localización del log (nombre de clase, método y línea)
+ independientemente de si luego se va a loguear o no (porque haya filtros por ejemplo) Esto es así porque el asyncappender
+ debe generar toda la info necesaria antes de pasar esta info a la cola. Una vez en la cola asíncrona esa info ya no puede
+ ser generada. Por tanto, aunque ese log finalmente no se use, siempre se pone en la cola asíncrona con todos sus datos.
+
+ Extracting location is an expensive operation (it can make logging 5 - 20 times slower). To improve performance, location
+ is not included by default when adding a log event to the queue. You can change this by setting includeLocation="true".
+ Para extraer la localización al parecer hay que recorrer el stacktrace por eso es una operacion pesada.
+ -->
+ <Appender type="Async" name="AsyncAppender" blocking="true" bufferSize="128" errorRef="FileAppender" ignoreExceptions="false" includeLocation="false">
+ <!--
+ Probar las dos configuraciones
+ <AppenderRef ref="RandomAccessFileAppender"/>
+ -->
+ <AppenderRef ref="FileAppender"/>
+ </Appender>
+
+ </Appenders>
+
+ <Loggers>
+ <!--
+ <Logger name="de.loggers.test" level="debug" additivity="false">
+ <Filter type="ThreadContextMapFilter">
+ <KeyValuePair key="test" value="123"/>
+ </Filter>
+ <AppenderRef ref="STDOUT"/>
+ </Logger>
+ -->
+
+ <!--
+ <Logger name="de.loggers.test" level="debug" additivity="false">
+ <AppenderRef ref="File"/>
+ </Logger>
+ -->
+
+ <Logger name="de.loggers.test.simple" level="TRACE">
+ <!--
+ * Because THIS logger has additivity="true" by default THIS logger will use the same appender as its parent's logger,
+ which in this case is the logger Root.
+ * Because THIS logger has additivity="true" by default if we use this AppenderRef we will see for
+ classes in de.loggers.test.simple the same messages twice. One from this logger and one from its parent's logger,
+ which in this case is the logger Root.
+ <AppenderRef ref="STDOUT"/>
+ -->
+ </Logger>
+ <Logger name="de.loggers.test.formatter" level="TRACE" additivity="false">
+ <!--
+ * Because THIS logger has additivity="false" THIS logger will not use the same appender as its parent's logger.
+ -->
+ <AppenderRef ref="STDOUT"/>
+ </Logger>
+ <!--
+ By default, location is not passed to the I/O thread by asynchronous loggers. If one of your layouts or custom
+ filters needs location information, you need to set "includeLocation=true" in the configuration of all relevant loggers,
+ including the root logger.
+ -->
+
+
+
+ <!--
+ * Asynchronous Loggers are a new addition to Log4j 2. Their aim is to return from the call to Logger.log to the application
+ as soon as possible. You can choose between making all Loggers asynchronous or using a mixture of synchronous and asynchronous
+ Loggers. Making all Loggers asynchronous will give the best performance, while mixing gives you more flexibility.
+ * LMAX Disruptor technology. Asynchronous Loggers internally use the Disruptor, a lock-free inter-thread communication library,
+ instead of queues, resulting in higher throughput and lower latency.
+ * Asynchronous Appenders already existed in Log4j 1.x, but have been enhanced to flush to disk at the end of a batch
+ (when the queue is empty). This produces the same result as configuring "immediateFlush=true", that is, all received log events
+ are always available on disk, but is more efficient because it does not need to touch the disk on each and every log event.
+ (Async Appenders use ArrayBlockingQueue internally and do not need the disruptor jar on the classpath.)
+ * (For synchronous and asynchronous use) Random Access File Appenders are an alternative to Buffered File Appenders. Under the hood,
+ these new appenders use a ByteBuffer + RandomAccessFile instead of a BufferedOutputStream. In our testing this was about 20-200%
+ faster. These appenders can also be used with synchronous loggers and will give the same performance benefits. Random Access
+ File Appenders do not need the disruptor jar on the classpath.
+ -->
+ <AsyncLogger name="de.loggers.test.loggernames" level="TRACE" includeLocation="false">
+ <AppenderRef ref="RandomAccessFileAppender"/>
+ </AsyncLogger>
+ <Root level="TRACE">
+ <AppenderRef ref="STDOUT"/>
+ </Root>
+ </Loggers>
+
+</Configuration>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- For assistance related to logback-translator or configuration -->
+<!-- files in general, please contact the logback user mailing list -->
+<!-- at http://www.qos.ch/mailman/listinfo/logback-user -->
+<!-- -->
+<!-- For professional support please see -->
+<!-- http://www.qos.ch/shop/products/professionalSupport -->
+<!-- -->
+<configuration>
+ <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
+ <encoder>
+ <pattern>%d{yyyyMMddHHmmssSSS} - %-5p - [%t] - [%C] - %m%n</pattern>
+ </encoder>
+ </appender>
+ <root level="INFO">
+ <appender-ref ref="stdout"/>
+ </root>
+</configuration>
--- /dev/null
+package de.example.services;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import de.example.model.mapper.UsersMapper;
+import de.example.model.resource.UserResource;
+import de.example.services.impl.UsersServiceImpl;
+
+
+public class UsersServiceTest {
+ private static final long ID = 10L;
+
+ private UsersService usersService;
+ private UsersMapper usersMapper;
+
+ @Before
+ public void setUp() throws Exception {
+ usersMapper = mock(UsersMapper.class);
+ // USING spy because we want to spy UsersService.spiedMethod
+ // otherwise we wouldn't need org.mockito.Mockito.spy.
+ usersService = spy(new UsersServiceImpl(usersMapper));
+ }
+
+ @Test
+ public void givenSomeIdWhenFindUserThenReturnUser() {
+ // Arrange
+ UserResource expectedUser = new UserResource(ID, "Gustavo");
+ when(usersMapper.findOne(ID)).thenReturn(expectedUser);
+
+ // Act
+ UserResource user = usersService.findOneById(ID);
+
+ // Assert
+ assertEquals(expectedUser, user);
+ }
+
+
+ @Test
+ public void givenNewUserDataWhenCreateNewUserThenCreateUser() {
+ // Arrange
+ UserResource expectedUser = new UserResource(ID, "Gustavo");
+ UserResource newUser = new UserResource(null, "Gustavo");
+
+ // Act
+ usersService.create(newUser);
+
+ // Assert
+ verify(usersService).spiedMethod(newUser);
+ verify(usersMapper).create(newUser);
+ assertEquals(expectedUser, newUser);
+ }
+
+ @Test(expected=NullPointerException.class)
+ public void givenNoUserDataWhenCreateNewUserThenThrowNullPointerException() {
+ // Act
+ usersService.create(null);
+ }
+
+ @Test
+ public void givenNewUserDataWhenCreateNewUserThenCreateUserWithArgumentCaptor() {
+ // Arrange
+ UserResource newUser = new UserResource(null, "Gustavo");
+ ArgumentCaptor<UserResource> argument = ArgumentCaptor.forClass(UserResource.class);
+
+ // Act
+ usersService.create(newUser);
+
+ // Assert
+ verify(usersService).spiedMethod(newUser);
+ verify(usersMapper).create(argument.capture());
+ assertEquals(argument.getValue(), newUser);
+ }
+}
--- /dev/null
+package de.example.tdd;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.List;
+
+import org.junit.Test;
+
+public class AddressBookTest {
+
+ @Test
+ public void testGivenAndExistingAddressBookWhenRetrievingTheContactsThenListWithContacts() {
+ //AddressBook addressBook = new AddressBook();
+
+
+ //List<Contact> contacts = addressBook.getAllContacts();
+ //int size = contacts.size();
+
+ // Assert
+ assertEquals(16, 16);
+ }
+}
--- /dev/null
+package de.example.tdd;
+
+import static org.junit.Assert.*;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class CalculatorTest {
+ private Calculator calculator;
+
+ @Before
+ public void setUp() throws Exception {
+ // Arrange
+ calculator = new Calculator();
+ }
+
+ @Test
+ public void testGivenTwoNumbersWhenAddingThenResult() {
+ // Arrange
+
+
+ // Act
+ int result = calculator.sum(2, 2);
+
+ // Assert
+ assertEquals(4, result);
+
+
+ // Act
+ result = calculator.sum(8, 8);
+
+ // Assert
+ assertEquals(16, result);
+ }
+
+ @Test
+ public void testGivenTwoNumbersWhenMultiplicationThenProduct() {
+
+
+ assertEquals(16, calculator.product(4, 4));
+
+ assertEquals(64, calculator.product(8, 8));
+ }
+}
--- /dev/null
+package de.example.tdd;
+
+import static org.junit.Assert.*;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class MoneyTest {
+
+ /**
+ * @throws java.lang.Exception
+ */
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception {
+ }
+
+ /**
+ * @throws java.lang.Exception
+ */
+ @AfterClass
+ public static void tearDownAfterClass() throws Exception {
+ }
+
+ /**
+ * @throws java.lang.Exception
+ */
+ @Before
+ public void setUp() throws Exception {
+ }
+
+ /**
+ * @throws java.lang.Exception
+ */
+ @After
+ public void tearDown() throws Exception {
+ }
+
+ @Test
+ public void testMultiplication()
+ {
+ Dollar five = new Dollar(5);
+ five.times(2);
+
+ assertEquals(10, five.amount);
+ }
+}