In Java, is there a generic way to embed the code of a method in a log by any means? I am working in Cucumber and altough its tending towards (or is?) bad practice, the compliance department wants to see the assertions behind a "Then" statement printed out in the report (they cant access the source code). Writing to a log is the easy part, but how can i read everything in a method behind a "@Then" in a generic way?
So just to give you an idea of an example method:
@Then("^my profile information is retrieved with success")
public void validateProfileInformation() {
assertThat(..).isEqualTo(..);
assertThat(..).isEqualTo(..);
assertThat(..).isEqualTo(..);
assertThat(..).isEqualTo(..);
assertThat(..).isEqualTo(..);
assertThat(..).isEqualTo(..);
if (this){
x = y;
}
}
What needs to be written in the log:
SOME HEADER TEXT
assertThat(..).isEqualTo(..);
assertThat(..).isEqualTo(..);
assertThat(..).isEqualTo(..);
assertThat(..).isEqualTo(..);
assertThat(..).isEqualTo(..);
assertThat(..).isEqualTo(..);
**Optionally:**
if (this){
x = y;
}
So i really need the unexecuted code (both when tests fail and pass), dont need the outcome. Preferably like a generic method or interceptor, so that it can be applied accross the project, rather than having to add it to each method. A push in the right direction would be appreciated.
<groupId>igb.so</groupId>
<artifactId>SO-62882091-log-sourcecode</artifactId>
<version>20.7.14</version>
<url>https://stackoverflow.com/a/62904271/1744774</url>
<developers>
<developer>
<name>Gerold 'Geri' Broser</name>
</developer>
</developers>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<log4j.version>2.13.3</log4j.version>
<slf4j.version>1.7.30</slf4j.version>
<junit.version>5.6.2</junit.version>
</properties>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
AbstractTestSourceErrorLogger
package igb.so.logging;
import java.util.Arrays;
import org.slf4j.Logger;
public abstract class AbstractTestSourceErrorLogger {
protected static final String LF = System.getProperty( "line.separator" );
protected final String testSourcePath;
protected final Class<? extends Object> testClass;
protected final Logger log;
protected boolean withLineNumbers;
protected int lineNo;
protected boolean isInMethod;
protected int codeBlockLevel;
protected AbstractTestSourceErrorLogger( final Class<? extends Object> testClass, final Logger logger ) {
this( "", testClass, logger, false );
}
protected AbstractTestSourceErrorLogger(
final String testSourcePath, final Class<? extends Object> testClass, final Logger logger ) {
this( testSourcePath, testClass, logger, false );
}
protected AbstractTestSourceErrorLogger(
final Class<? extends Object> testClass, final Logger logger, final boolean withLineNumbers ) {
this( "", testClass, logger, withLineNumbers );
}
protected AbstractTestSourceErrorLogger(
final String testSourcePath, final Class<? extends Object> testClass,
final Logger logger, final boolean withLineNumbers ) {
this.testSourcePath = testSourcePath;
this.testClass = testClass;
log = logger;
this.withLineNumbers = withLineNumbers;
}
/** Logs an {@link AssertionError} with the ERROR level. Afterwards logs the method body in which the error occurred.
*
* @param error - the error to log
*/
public void error( final AssertionError error ) {
error( error, withLineNumbers );
}
/** Logs an {@link AssertionError} with the ERROR level. Afterwards logs the method body in which the error occurred with optional line numbers.
*
* @param error - the error to log
* @param withLineNumbers - <code>true</code> for equipping the logged source code with line numbers
*/
public abstract void error( final AssertionError error, final boolean withLineNumbers );
protected StackTraceElement failedTest( final AssertionError error ) {
return Arrays.stream( error.getStackTrace() )
.filter( e -> e.getClassName().equals( testClass.getName() ) )
.findFirst().get();
}
} // AbstractTestSourceLogger
SimpleTestSourceErrorLogger
package igb.so.logging;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.slf4j.Logger;
public class SimpleTestSourceErrorLogger extends AbstractTestSourceErrorLogger {
public SimpleTestSourceErrorLogger(
final Class<? extends Object> testClass, final Logger logger ) {
super( testClass, logger );
}
public SimpleTestSourceErrorLogger(
final String testSourcePath, final Class<? extends Object> testClass, final Logger logger ) {
super( testSourcePath, testClass, logger, false );
}
public SimpleTestSourceErrorLogger(
final Class<? extends Object> testClass, final Logger logger, final boolean withLineNumbers ) {
super( "", testClass, logger, withLineNumbers );
}
public SimpleTestSourceErrorLogger(
final String testSourcePath, final Class<? extends Object> testClass,
final Logger logger, final boolean withLineNumbers ) {
super( testSourcePath, testClass, logger, withLineNumbers );
}
@Override
public void error( final AssertionError error, final boolean withLineNumbers ) {
final StringBuilder sb = new StringBuilder();
sb.append( error ).append( LF );
final StackTraceElement failedTest = failedTest( error );
final Path source = Paths.get( testSourcePath, failedTest.getClassName().replaceAll( "\\.", "/" ) + ".java" );
sb.append( " in method:" ).append( LF );
lineNo = 1;
try {
Files.lines( source )
.forEach( line -> {
if ( line.strip().startsWith( String.format( "public void %s()", failedTest.getMethodName() ) ) )
isInMethod = true;
if ( isInMethod ) {
if ( line.contains( "{" ) )
codeBlockLevel++;
if ( codeBlockLevel > 0 ) {
final String format = withLineNumbers ? " %d: %s%s%n" : "%2$s%3$s%n";
sb.append( String.format( format, lineNo, line,
lineNo == failedTest.getLineNumber() ? " // " + error.toString() : "" ) );
}
if ( line.contains( "}" ) )
codeBlockLevel--;
if ( codeBlockLevel <= 0 ) {
isInMethod = false;
}
}
lineNo++;
} );
log.error( sb.toString() );
}
catch (final IOException e) {
throw new RuntimeException( e );
}
} // error()
} // SimpleTestSourceErrorLogger
SimpleLoggerTest
package igb.so;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import igb.so.logging.SimpleTestSourceErrorLogger;
public class SimpleLoggerTest {
private static Logger log = LoggerFactory.getLogger( SimpleLoggerTest.class );
private static SimpleTestSourceErrorLogger sLog = //
new SimpleTestSourceErrorLogger( "src/test/java", SimpleLoggerTest.class, log, true );
@Test
public void testTrue() {
try {
assertTrue( true );
assertTrue( false );
}
catch (final AssertionError e) {
sLog.error( e );
throw e;
}
} // testTrue()
@Test
public void testNotNull() {
try {
assertNotNull( new Object() );
assertNotNull( null );
}
catch (final AssertionError e) {
sLog.error( e, false );
throw e;
}
} // testNotNull()
} // SimpleLoggerTest
23:16:02.035 [main] ERROR igb.so.SimpleLoggerTest - org.opentest4j.AssertionFailedError: expected: <true> but was: <false>
in method:
18: public void testTrue() {
19:
20: try {
21: assertTrue( true );
22: assertTrue( false ); // org.opentest4j.AssertionFailedError: expected: <true> but was: <false>
23: }
24: catch (final AssertionError e) {
25: sLog.error( e );
26: throw e;
27: }
28:
29: } // testTrue()
23:16:02.111 [main] ERROR igb.so.SimpleLoggerTest - org.opentest4j.AssertionFailedError: expected: not <null>
in method:
public void testNotNull() {
try {
assertNotNull( new Object() );
assertNotNull( null ); // org.opentest4j.AssertionFailedError: expected: not <null>
}
catch (final AssertionError e) {
sLog.error( e, false );
throw e;
}
} // testNotNull()
mvn test
output...
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running igb.so.SimpleLoggerTest
23:25:25.956 [main] ERROR igb.so.SimpleLoggerTest - org.opentest4j.AssertionFailedError: expected: <true> but was: <false>
in method:
18: public void testTrue() {
19:
20: try {
21: assertTrue( true );
22: assertTrue( false ); // org.opentest4j.AssertionFailedError: expected: <true> but was: <false>
23: }
24: catch (final AssertionError e) {
25: sLog.error( e );
26: throw e;
27: }
28:
29: } // testTrue()
23:25:25.966 [main] ERROR igb.so.SimpleLoggerTest - org.opentest4j.AssertionFailedError: expected: not <null>
in method:
public void testNotNull() {
try {
assertNotNull( new Object() );
assertNotNull( null ); // org.opentest4j.AssertionFailedError: expected: not <null>
}
catch (final AssertionError e) {
sLog.error( e, false );
throw e;
}
} // testNotNull()
Tests run: 2, Failures: 2, Errors: 0, Skipped: 0, Time elapsed: 0.026 sec <<< FAILURE!
igb.so.SimpleLoggerTest.testTrue() Time elapsed: 0.023 sec <<< FAILURE!
org.opentest4j.AssertionFailedError: expected: <true> but was: <false>
at org.junit.jupiter.api.AssertionUtils.fail(AssertionUtils.java:55)
at org.junit.jupiter.api.AssertTrue.assertTrue(AssertTrue.java:40)
at org.junit.jupiter.api.AssertTrue.assertTrue(AssertTrue.java:35)
at org.junit.jupiter.api.Assertions.assertTrue(Assertions.java:162)
at igb.so.SimpleLoggerTest.testTrue(SimpleLoggerTest.java:22)
igb.so.SimpleLoggerTest.testNotNull() Time elapsed: 0.001 sec <<< FAILURE!
org.opentest4j.AssertionFailedError: expected: not <null>
at org.junit.jupiter.api.AssertionUtils.fail(AssertionUtils.java:39)
at org.junit.jupiter.api.Assertions.fail(Assertions.java:109)
at org.junit.jupiter.api.AssertNotNull.failNull(AssertNotNull.java:47)
at org.junit.jupiter.api.AssertNotNull.assertNotNull(AssertNotNull.java:36)
at org.junit.jupiter.api.AssertNotNull.assertNotNull(AssertNotNull.java:31)
at org.junit.jupiter.api.Assertions.assertNotNull(Assertions.java:283)
at igb.so.SimpleLoggerTest.testNotNull(SimpleLoggerTest.java:36)
Results :
Failed tests: igb.so.SimpleLoggerTest.testTrue(): expected: <true> but was: <false>
igb.so.SimpleLoggerTest.testNotNull(): expected: not <null>
Tests run: 2, Failures: 2, Errors: 0, Skipped: 0
...
PS: Using JUnit since I'm not familiar with Cucumber. But you get the idea.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With