[examples] Add unit testing infrastructure (#4646)

This commit is contained in:
Starlight220
2022-12-02 18:40:14 +02:00
committed by GitHub
parent 74cc86c4c5
commit 66bb0ffb2c
15 changed files with 444 additions and 13 deletions

View File

@@ -733,6 +733,17 @@
"mainclass": "Main",
"commandversion": 2
},
{
"name": "UnitTesting",
"description": "Demonstrates basic unit testing for a robot project.",
"tags": [
"Testing"
],
"foldername": "unittest",
"gradlebase": "java",
"mainclass": "Main",
"commandversion": 2
},
{
"name": "DifferentialDrivePoseEstimator",
"description": "Demonstrates the use of the DifferentialDrivePoseEstimator as a replacement for differential drive odometry.",

View File

@@ -0,0 +1,25 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.wpilibj.examples.unittest;
/**
* The Constants class provides a convenient place for teams to hold robot-wide numerical or boolean
* constants. This class should not be used for any other purpose. All constants should be declared
* globally (i.e. public static). Do not put anything functional in this class.
*
* <p>It is advised to statically import this class (or one of its inner classes) wherever the
* constants are needed, to reduce verbosity.
*/
public final class Constants {
public static final int kJoystickIndex = 0;
public static final class IntakeConstants {
public static final int kMotorPort = 1;
public static final int kPistonFwdChannel = 0;
public static final int kPistonRevChannel = 1;
public static final double kIntakeSpeed = 0.5;
}
}

View File

@@ -0,0 +1,25 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.wpilibj.examples.unittest;
import edu.wpi.first.wpilibj.RobotBase;
/**
* Do NOT add any static variables to this class, or any initialization at all. Unless you know what
* you are doing, do not modify this file except to change the parameter class to the startRobot
* call.
*/
public final class Main {
private Main() {}
/**
* Main initialization function. Do not perform any initialization here.
*
* <p>If you change your main robot class, change the parameter type.
*/
public static void main(String... args) {
RobotBase.startRobot(Robot::new);
}
}

View File

@@ -0,0 +1,41 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.wpilibj.examples.unittest;
import edu.wpi.first.wpilibj.Joystick;
import edu.wpi.first.wpilibj.TimedRobot;
import edu.wpi.first.wpilibj.examples.unittest.Constants.IntakeConstants;
import edu.wpi.first.wpilibj.examples.unittest.subsystems.Intake;
/**
* The VM is configured to automatically run this class, and to call the functions corresponding to
* each mode, as described in the TimedRobot documentation. If you change the name of this class or
* the package after creating this project, you must also update the build.gradle file in the
* project.
*/
public class Robot extends TimedRobot {
private Intake m_intake = new Intake();
private Joystick m_joystick = new Joystick(Constants.kJoystickIndex);
/** This function is called periodically during operator control. */
@Override
public void teleopPeriodic() {
// Activate the intake while the trigger is held
if (m_joystick.getTrigger()) {
m_intake.activate(IntakeConstants.kIntakeSpeed);
} else {
m_intake.activate(0);
}
// Toggle deploying the intake when the top button is pressed
if (m_joystick.getTop()) {
if (m_intake.isDeployed()) {
m_intake.retract();
} else {
m_intake.deploy();
}
}
}
}

View File

@@ -0,0 +1,51 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.wpilibj.examples.unittest.subsystems;
import edu.wpi.first.wpilibj.DoubleSolenoid;
import edu.wpi.first.wpilibj.PneumaticsModuleType;
import edu.wpi.first.wpilibj.examples.unittest.Constants.IntakeConstants;
import edu.wpi.first.wpilibj.motorcontrol.PWMSparkMax;
public class Intake implements AutoCloseable {
private final PWMSparkMax m_motor;
private final DoubleSolenoid m_piston;
public Intake() {
m_motor = new PWMSparkMax(IntakeConstants.kMotorPort);
m_piston =
new DoubleSolenoid(
PneumaticsModuleType.CTREPCM,
IntakeConstants.kPistonFwdChannel,
IntakeConstants.kPistonRevChannel);
}
public void deploy() {
m_piston.set(DoubleSolenoid.Value.kForward);
}
public void retract() {
m_piston.set(DoubleSolenoid.Value.kReverse);
m_motor.set(0); // turn off the motor
}
public void activate(double speed) {
if (isDeployed()) {
m_motor.set(speed);
} else { // if piston isn't open, do nothing
m_motor.set(0);
}
}
public boolean isDeployed() {
return m_piston.get() == DoubleSolenoid.Value.kForward;
}
@Override
public void close() throws Exception {
m_piston.close();
m_motor.close();
}
}

View File

@@ -0,0 +1,70 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.wpilibj.examples.unittest.subsystems;
import static org.junit.jupiter.api.Assertions.assertEquals;
import edu.wpi.first.hal.HAL;
import edu.wpi.first.wpilibj.DoubleSolenoid;
import edu.wpi.first.wpilibj.PneumaticsModuleType;
import edu.wpi.first.wpilibj.examples.unittest.Constants.IntakeConstants;
import edu.wpi.first.wpilibj.simulation.DoubleSolenoidSim;
import edu.wpi.first.wpilibj.simulation.PWMSim;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class IntakeTest {
static final double DELTA = 1e-2; // acceptable deviation range
Intake m_intake;
PWMSim m_simMotor;
DoubleSolenoidSim m_simPiston;
@BeforeEach // this method will run before each test
void setup() {
assert HAL.initialize(500, 0); // initialize the HAL, crash if failed
m_intake = new Intake(); // create our intake
m_simMotor =
new PWMSim(IntakeConstants.kMotorPort); // create our simulation PWM motor controller
m_simPiston =
new DoubleSolenoidSim(
PneumaticsModuleType.CTREPCM,
IntakeConstants.kPistonFwdChannel,
IntakeConstants.kPistonRevChannel); // create our simulation solenoid
}
@SuppressWarnings("PMD.SignatureDeclareThrowsException")
@AfterEach // this method will run after each test
void shutdown() throws Exception {
m_intake.close(); // destroy our intake object
}
@Test // marks this method as a test
void doesntWorkWhenClosed() {
m_intake.retract(); // close the intake
m_intake.activate(0.5); // try to activate the motor
assertEquals(
0.0, m_simMotor.getSpeed(), DELTA); // make sure that the value set to the motor is 0
}
@Test
void worksWhenOpen() {
m_intake.deploy();
m_intake.activate(0.5);
assertEquals(0.5, m_simMotor.getSpeed(), DELTA);
}
@Test
void retractTest() {
m_intake.retract();
assertEquals(DoubleSolenoid.Value.kReverse, m_simPiston.get());
}
@Test
void deployTest() {
m_intake.deploy();
assertEquals(DoubleSolenoid.Value.kForward, m_simPiston.get());
}
}