Skip to content

Commit

Permalink
Merge pull request #8 from giograno/master
Browse files Browse the repository at this point in the history
Detection thresholds and numerical detection
  • Loading branch information
shehan authored Oct 21, 2021
2 parents 19adeb4 + f240ed8 commit e08319f
Show file tree
Hide file tree
Showing 42 changed files with 2,944 additions and 668 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ hs_err_pid*
Output_TestSmellDetection*
target/
TestSmellDetector.iml
files-smell.csv
files-smell.csv
test-smells.csv
28 changes: 25 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,38 @@ Unit test code, just like any other source code, is subject to bad programming p

Test smells are defined as bad programming practices in unit test code (such as how test cases are organized, implemented and interact with each other) that indicate potential design problems in the test source code.



## Project Overview

The purpose of this project is twofold:

1. Contribute to the list of existing test smells, by proposing new test smells that developers need to be aware of.
2. Provide developers with a tool to automatically detect test smell in their unit test code.


## More Information

Visit the project website: https://testsmells.github.io/

## Execution

Running the jar with `--help` will print its usage.

* A CSV input file always need to be given as parameter, specified with `-f`;
* A detection threshold can also be specified. Possible values are `default` and `spadini`. The flag is `-t`.
By default, the tool uses the thresholds that have been originally implemented;
with `spadini`, sensibility thresholds published by [Spadini et.al.] will be used.
* One can specify the granularity of the detection. `boolean` will return either true or false, respectively if a
given smell is present or not in the test; `numerical` will return instead the number of smelly instances detected.

```
Options:
-f, --file PATH The csv input file
-t, --thresholds [default|spadini]
The threshold to use for the detection
-g, --granularity [boolean|numerical]
Boolean value of numerical for the
detection
-o, --output TEXT
-h, --help Show this message and exit
```

[Spadini et.al.]: https://dl.acm.org/doi/abs/10.1145/3379597.3387453
122 changes: 98 additions & 24 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,97 @@
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>

<properties>
<kotlin.version>1.4.20</kotlin.version>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven-surefire-plugin.version>2.22.2</maven-surefire-plugin.version>
<maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
<junit-jupiter.version>5.7.0</junit-jupiter.version>
<kotlin.compiler.jvmTarget>1.8</kotlin.compiler.jvmTarget>
</properties>

<groupId>edu.rit.se.testsmells</groupId>
<artifactId>TestSmellDetector</artifactId>
<version>0.1</version>
<packaging>jar</packaging>
<build>
<plugins>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
</plugin>

<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<sourceDirs>
<sourceDir>${project.basedir}/src/main/kotlin</sourceDir>
<sourceDir>${project.basedir}/src/main/java</sourceDir>
</sourceDirs>
</configuration>
</execution>
<execution>
<id>test-compile</id>
<goals>
<goal>test-compile</goal>
</goals>
<configuration>
<sourceDirs>
<sourceDir>${project.basedir}/src/test/kotlin</sourceDir>
<sourceDir>${project.basedir}/src/test/java</sourceDir>
</sourceDirs>
</configuration>
</execution>
</executions>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
<executions>
<!-- Replacing default-compile as it is treated specially by maven -->
<execution>
<id>default-compile</id>
<phase>none</phase>
</execution>
<!-- Replacing default-testCompile as it is treated specially by maven -->
<execution>
<id>default-testCompile</id>
<phase>none</phase>
</execution>
<execution>
<id>java-compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>java-test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M4</version>
<dependencies>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>5.4.2</version>
</dependency>
</dependencies>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
Expand All @@ -43,7 +108,7 @@
<archive>
<manifest>
<mainClass>
Main
testsmell.RunnerKt
</mainClass>
</manifest>
</archive>
Expand All @@ -69,23 +134,32 @@
<version>3.9</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit-jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.4.2</version>
<scope>test</scope>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.4.2</version>
<groupId>com.github.ajalt</groupId>
<artifactId>clikt</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>com.github.doyaaaaaken</groupId>
<artifactId>kotlin-csv-jvm</artifactId>
<version>0.13.0</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.2.4</version>
<scope>test</scope>
</dependency>
</dependencies>

</project>
26 changes: 13 additions & 13 deletions src/main/java/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
import testsmell.ResultsWriter;
import testsmell.TestFile;
import testsmell.TestSmellDetector;
import testsmell.smell.AssertionRoulette;
import testsmell.smell.EagerTest;
import thresholds.DefaultThresholds;
import thresholds.Thresholds;

import java.io.BufferedReader;
import java.io.File;
Expand All @@ -21,15 +21,15 @@ public static void main(String[] args) throws IOException {
System.out.println("Please provide the file containing the paths to the collection of test files");
return;
}
if(!args[0].isEmpty()){
if (!args[0].isEmpty()) {
File inputFile = new File(args[0]);
if(!inputFile.exists() || inputFile.isDirectory()) {
if (!inputFile.exists() || inputFile.isDirectory()) {
System.out.println("Please provide a valid file containing the paths to the collection of test files");
return;
}
}

TestSmellDetector testSmellDetector = new TestSmellDetector();
TestSmellDetector testSmellDetector = new TestSmellDetector(new DefaultThresholds());

/*
Read the input file and build the TestFile objects
Expand All @@ -45,10 +45,9 @@ public static void main(String[] args) throws IOException {
lineItem = str.split(",");

//check if the test file has an associated production file
if(lineItem.length ==2){
if (lineItem.length == 2) {
testFile = new TestFile(lineItem[0], lineItem[1], "");
}
else{
} else {
testFile = new TestFile(lineItem[0], lineItem[1], lineItem[2]);
}

Expand All @@ -69,6 +68,7 @@ public static void main(String[] args) throws IOException {
columnNames.add(3, "ProductionFilePath");
columnNames.add(4, "RelativeTestFilePath");
columnNames.add(5, "RelativeProductionFilePath");
columnNames.add(6, "NumberOfMethods");

resultsWriter.writeColumnName(columnNames);

Expand All @@ -80,8 +80,8 @@ public static void main(String[] args) throws IOException {
Date date;
for (TestFile file : testFiles) {
date = new Date();
System.out.println(dateFormat.format(date) + " Processing: "+file.getTestFilePath());
System.out.println("Processing: "+file.getTestFilePath());
System.out.println(dateFormat.format(date) + " Processing: " + file.getTestFilePath());
System.out.println("Processing: " + file.getTestFilePath());

//detect smells
tempFile = testSmellDetector.detectSmells(file);
Expand All @@ -94,11 +94,11 @@ public static void main(String[] args) throws IOException {
columnValues.add(file.getProductionFilePath());
columnValues.add(file.getRelativeTestFilePath());
columnValues.add(file.getRelativeProductionFilePath());
columnValues.add(String.valueOf(file.getNumberOfTestMethods()));
for (AbstractSmell smell : tempFile.getTestSmells()) {
try {
columnValues.add(String.valueOf(smell.getHasSmell()));
}
catch (NullPointerException e){
columnValues.add(String.valueOf(smell.getNumberOfSmellyTests()));
} catch (NullPointerException e) {
columnValues.add("");
}
}
Expand Down
39 changes: 35 additions & 4 deletions src/main/java/testsmell/AbstractSmell.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,47 @@
package testsmell;

import com.github.javaparser.ast.CompilationUnit;
import thresholds.Thresholds;

import java.io.FileNotFoundException;
import java.util.List;
import java.util.HashSet;
import java.util.Set;

public abstract class AbstractSmell {
protected Thresholds thresholds;
protected Set<SmellyElement> smellyElementsSet;

public AbstractSmell(Thresholds thresholds) {
this.thresholds = thresholds;
this.smellyElementsSet = new HashSet<>();
}

public abstract String getSmellName();

public abstract boolean getHasSmell();
/**
* Return 1 if any of the elements has a smell; 0 otherwise
*/
public boolean hasSmell() {
return smellyElementsSet.stream().filter(SmellyElement::isSmelly).count() >= 1;
}

public abstract void runAnalysis(CompilationUnit testFileCompilationUnit,
CompilationUnit productionFileCompilationUnit,
String testFileName,
String productionFileName) throws FileNotFoundException;

public abstract void runAnalysis(CompilationUnit testFileCompilationUnit,CompilationUnit productionFileCompilationUnit, String testFileName, String productionFileName) throws FileNotFoundException;
/**
* Returns the set of analyzed elements (i.e. test methods)
*/
public Set<SmellyElement> getSmellyElements() {
return smellyElementsSet;
}

public abstract List<SmellyElement> getSmellyElements();
/**
* Returns the number of test cases in a test suite (jUnit test file).
* In theory, it counts all the smelly elements (i.e., the methods), that are smelly
*/
public int getNumberOfSmellyTests() {
return (int) smellyElementsSet.stream().filter(SmellyElement::isSmelly).count();
}
}
18 changes: 18 additions & 0 deletions src/main/java/testsmell/DetectionThresholds.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package testsmell;

/**
* There are the thresholds for the test smells detection proposed by Spadini et.al. in
* the paper _Investigating severity thresholds for test smells_.
*/
public class DetectionThresholds {

public static int EAGER_TEST = 4;
public static int ASSERTION_ROULETTE = 3;
public static int VERBOSE_TEST = 13;
public static int CONDITIONAL_TEST_LOGIC = 0;
public static int MAGIC_NUMBER_TEST = 0;
public static int GENERAL_FIXTURE = 0;
public static int MYSTERY_GUEST = 0;
public static int RESOURCE_OPTIMISM = 0;
public static int SLEEPY_TEST = 0;
}
2 changes: 1 addition & 1 deletion src/main/java/testsmell/SmellyElement.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
public abstract class SmellyElement {
public abstract String getElementName();

public abstract boolean getHasSmell();
public abstract boolean isSmelly();

public abstract Map<String, String> getData();
}
2 changes: 1 addition & 1 deletion src/main/java/testsmell/TestClass.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public String getElementName() {
}

@Override
public boolean getHasSmell() {
public boolean isSmelly() {
return hasSmell;
}

Expand Down
Loading

0 comments on commit e08319f

Please sign in to comment.