我正在编写一个应用程序 - 我认为我的设计并不好。我使用一个名为ExposedFunctions的类,它包含我的@WebMethod
逻辑,用于请求我的Web服务。这些方法通常需要从内部缓存读取或转到数据库。因此,我有一个DBFactory类,它有各自的数据库操作方法。这种结构的一个例子可以在下面看到
ExposedFunctions
@WebService
public class ExposedFunctions {
private static Logger logger = Logger.getLogger(ExposedFunctions.class);
private DBFactory factory = new DBFactory();
@WebMethod
public String register(String username, String password, String email, String firstname, String lastname) {
if(StringUtilities.stringEmptyOrNull(username) ||
StringUtilities.stringEmptyOrNull(password) ||
StringUtilities.stringEmptyOrNull(email) ||
StringUtilities.stringEmptyOrNull(firstname) ||
StringUtilities.stringEmptyOrNull(lastname)){
logger.error("String was null or empty when registering");
}
RegistrationStatus status = factory.register(username, password, email, firstname, lastname);
return status.getValue();
}
}
DBFactory
public class DBFactory {
private final BasicDataSource source = new BasicDataSource();
private final Logger logger = Logger.getLogger(DBFactory.class);
public DBFactory() {
try {
setupConnections();
} catch (DatabasePropertyException e) {
logger.info("Unable to load the properties file", e);
System.exit(1);
}
}
private void setupConnections() throws DatabasePropertyException{
Properties props = DatabaseUtilities.getDatabaseConnectionProps("/swiped.properties");
if(props != null){
source.setDriverClassName("com.mysql.jdbc.Driver");
source.setUsername(props.getProperty("username"));
source.setPassword(props.getProperty("password"));
source.setUrl(props.getProperty("url_local"));
source.setMaxActive(-1);
}else{
throw new DatabasePropertyException("Unable to load the proeprties file in order to connect to the database - exiting application");
}
}
public RegistrationStatus register(String username, String password, String email, String firstname, String lastname) {
String sql = "INSERT INTO Users (username, password, email, firstname, lastname) values (?,?,?,?,?)";
RegistrationStatus status = null;
Connection conn = null;
PreparedStatement st = null;
try {
conn = source.getConnection();
st = conn.prepareStatement(sql);
st.setString(1, username);
st.setString(2, password);
st.setString(3, email);
st.setString(4, firstname);
st.setString(5, lastname);
st.executeUpdate();
status = RegistrationStatus.SUCCESSFUL;
}
catch (MySQLIntegrityConstraintViolationException e) {
logger.warn("Unable to register user " + username + " as they are already registered");
return RegistrationStatus.USER_ALREADY_REGISTERED;
}catch(Exception e){
logger.error("Unable to insert a new user in to the database", e);
status = RegistrationStatus.FAILED;
}finally{
DatabaseUtilities.closeConnection(conn);
DatabaseUtilities.closePreparedStatement(st);
}
return status;
}
这个设置使得我很难进行测试,因为1)DBFactory与特定的数据库连接相关联 - 这意味着内部数据很可能会发生变化,测试将不定期地通过和失败。还有一个问题是DBFactory可以达到2000多行代码,这也不是理想的。
请有人建议一些改进此设计的方法,以增加/最大化我的测试工作,并帮助确保更好的可维护性和可扩展性。
由于
答案 0 :(得分:1)
嗯,您需要测试至少3个级别,并且需要对代码进行一些小修改以使其更容易:
DbFactory
。很高兴您已经抽象了实际与数据库交互的代码,因为这样可以更容易地进行测试。如果您修改ExposedFunctions
以便在测试期间传入或注入不同的DbFactory
实例,那么您的测试可以使用模拟DbFactory
。模拟工厂可以确保您的代码传递正确的参数,您可以测试各种错误条件,而无需实际触及真实的数据库。
您可能需要做的就是添加构造函数或setter方法来修改dbFactory字段:
public class ExposedFunctions {
...
private DBFactory factory;
public ExposedFunctions(){
this(new DBFactory());
}
public ExposedFunctions(DbFactory factory){
Objects.requireNonNull(factory);
this.factory = factory;
}
...
}
DbFactory
课程以模拟Connection
如果你在DbFactory
课程中做了类似的事情,那么你可以模拟Connection
,那么你对DbFactory
的测试也不必打到真正的数据库。通过模拟连接及其返回的语句,您可以验证是否正在为给定参数执行正确的SQL代码,以及测试各种SQLException条件以模拟在现实世界中难以测试的连接或数据问题。 / p>
实际连接到真实数据库以确保一切都作为集成测试的一部分工作也是一个好主意。不要连接到生产!您可以使用不同的属性文件或注入#2中的其他连接对象来交换连接。有dbUnit这样的库可以帮助处理测试数据库。还有像derby或hsqldb这样的内存数据库,它们是轻量级的,让您比连接到“常规”数据库更快地运行测试。但是,对于内存中的dbs,请注意一点,它与您选择的数据库供应商的环境或SQL解释器不完全相同。 SQL或模式设计中可能存在差异,这可能会让您认为自测试通过后您的代码可以正常工作,但是在使用相同代码的生产中出现了错误。
希望这有帮助