【C#|.NET】從細節出發(三) 邏輯層事務和page object模式
2014-07-17 12:29 熬夜的蟲子 閱讀(1975) 評論(0) 收藏 舉報一. 業務邏輯層的事務問題
如果你的程序分層清晰并且系統禁用復雜存儲過程,那么在DA中的職責比較單一。程序的邏輯通過BLL調用各種不同模塊的DA來實現數據操作。如果當需要不同模塊在一個事務的時候,問題就產生了。
如果你在bll引用System.Data...或者你在DA中穿插各種復雜邏輯的時候基本上你的工程已經不能算是好的程序了,
1. 使用TransactionScope
TransactionScope可以使代碼塊成為事務性代碼。但是需要開通MSDTC權限,并且TransactionScope的性能問題也一直存在爭議。
2. 階段性提交
這個涉及到框架層次的修改,會單獨開篇幅來說
3. 使用委托 將多模塊提交匯聚在一起 和框架層次的階段性提交殊途同歸
這個就是設計上的問題,將其他模塊的方法以委托方式傳入DA。舉個例子假如主線為消費記錄ConsumeRecord,分支線為消費詳細ConsumeRecordDetail和票據信息TravelTicket。其中TravelTicket完全為另一個服務模塊對自己為黑盒。ConsumeRecordDetail對自己為白盒。
DA層
public delegate BizResult<string> ArchiveTravelTicketDelegate( DbTransaction transaction, List<string> travelTicketIDList);
主線服務BLL中,其中StartArchives為其他模塊但是需要包含近事務的方法
private BizResult<string> InsertConsumeRecord(ConsumeRecordEntity consumeRecordEntity, List<ConsumeRecordDetailEntity> consumeRecordDetails)
{
return consumeRecordArchiveTargetDA.DoArchive(consumeRecordEntity, consumeRecordDetails, new TravelTicketArchiveService().StartArchives);
}
主線服務DA中
public BizResult<string> DoArchive(ConsumeRecordEntity consumeRecordEntity, List<ConsumeRecordDetailEntity> consumeRecordDetails,
ArchiveHelper.ArchiveTravelTicketDelegate archiveTravelTicket) { return ArchiveHandle(consumeRecordEntity, consumeRecordDetails, true, ConsumeRecordHandle, archiveTravelTicket); }
public BizResult<string> ConsumeRecordHandle(DbTransaction currentTransaction, ConsumeRecordEntity consumeRecordEntity, List<ConsumeRecordDetailEntity> consumeRecordDetails)
{
return ConsumeRecordHandle(currentTransaction, consumeRecordEntity, consumeRecordDetails,
(x, y) => InsertConsumeRecordLog(currentTransaction, consumeRecordEntity, true, false)
, InsertConsumeRecord, consumeRecordDetailArchiveDA.InsertConsumeRecordDetail);
}
其中InsertConsumeRecordLog,InsertConsumeRecord為主線自己的方法,ConsumeRecordDetail因為白盒可以直接使用consumeRecordDetailArchiveDA.InsertConsumeRecordDetail,archiveTravelTicket為黑盒。
主線BaseDA中
public BizResult<string> ArchiveHandle(ConsumeRecordEntity consumeRecordEntity, List<ConsumeRecordDetailEntity> consumeRecordDetails, bool isArchive,
ConsumeRecordHandlerDelegate consumeRecordHandlerDelegate, ArchiveHelper.ArchiveTravelTicketDelegate archiveTravelTicket)
{
using (DbConnection dbConnection = DbObject.CreateConnection())
{
dbConnection.Open();
BizResult<string> bizResult;
using (DbTransaction currentTransaction = dbConnection.BeginTransaction())
{
try
{
bizResult = consumeRecordHandlerDelegate(currentTransaction, consumeRecordEntity, consumeRecordDetails);
if (bizResult.IsSuccessful)
{
if (archiveTravelTicket != null)
{
bizResult = archiveTravelTicket(currentTransaction,
consumeRecordDetails.Select(x => x.TravelMoneyID).
Distinct().ToList());
}
if (bizResult.IsSuccessful)
{
bizResult.Message = "xxx success";
currentTransaction.Commit();
return bizResult;
}
bizResult.Message = "xxx fail";
}
bizResult.Message = string.Format("{0}:{1}", isArchive, bizResult.Message);
currentTransaction.Rollback();
}
catch (Exception ex)
{
logger.Error(ex);
currentTransaction.Rollback();
throw;
}
}
return bizResult;
}
}
二. 測試環節的page object模式
拿自動化測試Selenium為例,一般而言我們對于程序都是應付測試用例,針對測試用例寫出一個一個test-method。這樣無可厚非,使用page object模式可以讓代碼開起來更專業更精彩。
在自動化測試中,主要關注UI的測試互動區域。 Page對象只是這些模型作為測試代碼中的對象可以減少了重復的代碼量,并且意味著,如果用戶界面的變化,也只需要修改一個地方。和軟件設計模式中dry(don't repeat yourself)類似。并且在page object模式中,PageObjects也可以實現頁于頁之間的邏輯。總得來說減少了代碼的重復,讓測試更具可讀性和強大的,提高了測試的可維護性,特別是當有頻繁變化的ui變更。
舉個列子(java版本)
public class LoginPageTest {
private LoginPage loginPage;
private final String username = "xx@163.com";
private final String pwd= "Welcome1";
@Before
public void setUp() throws Exception {
String applicationRoot = new File(
getClass().getProtectionDomain().getCodeSource().getLocation().getPath())
.getParent();
System.setProperty("webdriver.chrome.driver", applicationRoot + "/chromedriver");
WebDriver webDriver = new ChromeDriver();
loginPage = new LoginPage(webDriver);
loginPage.init();
}
@Test
public void testLoginWithoutFakeCheckbox()
{
loginPage.setUserName(username);
loginPage.setPassword(pwd);
loginPage.pressLoginButton();
assertThat(loginPage.getClassByLoginWithoutFakeCheckbox(), equalTo("checkbox-wrapper not-checked"));
}
@Test
public void testLoginWithWrongUsername()
{
loginPage.setUserName("wrongusername");
loginPage.setPassword(pwd);
loginPage.pressCheckBox();
loginPage.pressLoginButton();
assertThat(loginPage.getClassByLoginWithWrongUserName(), equalTo("wrapper-input-text wrong"));
}
@Test
public void testLoginWithNullPwd()
{
loginPage.clearInput();
loginPage.setUserName(username);
loginPage.pressCheckBox();
loginPage.pressLoginButton();
assertThat(loginPage.getClassByLoginWithNullPwd(), equalTo("wrapper-input-text wrong"));
}
@Test
public void testLoginWithRightWay()
{
loginPage.clearInput();
loginPage.setUserName(username);
loginPage.setPassword(pwd);
loginPage.pressCheckBox();
loginPage.pressLoginButton();
assertThat(loginPage.getTextByRightWay(), equalTo("選擇性別"));
}
}
public class LoginPage {
private final WebDriver webDriver;
private final WebDriverWait wait;
private JavascriptExecutor js;
private final String username = "xx@163.com";
private final String pwd = "Welcome1";
private WebElement webElementLogin;
private WebElement webElementPwd;
private WebElement webElementInput;
public LoginPage(WebDriver webDriver) {
this.webDriver = webDriver;
this.wait = new WebDriverWait(webDriver, 10);
this.js = (JavascriptExecutor) webDriver;
}
public void init() {
webDriver.get("http://root:root@localhost:8080/apitool/xxx/owF3PjkBFY5_56Q_KYs5fxmLZTKI");
WebElement webElementName = wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("p.text.bold.name")));
if (webElementName != null) {
System.out.print(webElementName.getText());
}
webElementLogin = webDriver.findElement(By.cssSelector("input.input-text.login-input"));
webElementLogin.sendKeys(username);
webElementPwd = webDriver.findElement(By.cssSelector("input.input-text.password-input"));
webElementPwd.sendKeys(pwd);
}
public String getClassByLoginWithoutFakeCheckbox() {
WebElement webElementCss = wait.until(
ExpectedConditions.visibilityOfElementLocated(
By.xpath("http://*[contains(@class,'checkbox-wrapper not-checked')]")));
WebElement webElementHref = webDriver.findElement(By.xpath("http://*[@id=\"login-page\"]/div/section/div/div[5]/div/div"));
return webElementHref.getAttribute("class");
}
public String getClassByLoginWithWrongUserName() {
webElementInput = webDriver.findElement(By.xpath("http://*[@id=\"login-page\"]/div/section/div/div[3]"));
return webElementInput.getAttribute("class");
}
public String getClassByLoginWithNullPwd() {
webElementInput = webDriver.findElement(By.xpath("http://*[@id=\"login-page\"]/div/section/div/div[3]"));
return webElementInput.getAttribute("class");
}
public String getTextByRightWay() {
GenderViewPage genderViewPage = new GenderViewPage(webDriver);
return genderViewPage.getGenderText();
}
public void pressCheckBox() {
js.executeScript("$('.fake-checkbox').attr('class','fake-checkbox active')");
}
public void clearInput() {
webElementLogin.clear();
webElementPwd.clear();
}
public void setUserName(String username) {
webElementLogin.clear();
webElementLogin.sendKeys(username);
}
public void setPassword(String password) {
webElementPwd.clear();
webElementPwd.sendKeys(pwd);
}
public void pressLoginButton() {
js.executeScript("$('.login-button').trigger('touchstart')");
}
public void closeBrowser()
{
webDriver.quit();
}
}
三. sql update 慎用位運算
update t_User set valye = Flag | 4 where UserID = 1
如果Flag字段中存在負數,在做位運算的時候,更新出非常大的數值,超過2的62次方
希望對大家有幫助。
![]() |
原創作品允許轉載,轉載時請務必以超鏈接形式標明文章原始出處以及作者信息。 作者:熬夜的蟲子 點擊查看:博文索引 |

浙公網安備 33010602011771號